gplus

package module
v0.8.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: MIT Imports: 11 Imported by: 0

README

GPlus - Go GORM 增强库

GPlus 是一个基于 GORM 的 Go 语言增强库,提供类型安全的查询构建器、Repository 模式和条件构建等功能,让数据库操作更加简洁、类型安全和高效。

特性

  • 🚀 类型安全查询:通过泛型实现类型安全的查询构建
  • 📦 Repository 模式:标准化的 CRUD 操作接口
  • 🏗️ 流畅的条件构建:链式调用构建复杂查询条件
  • 🔒 事务支持:无缝的事务管理
  • 高性能:智能缓存和优化,减少反射开销
  • 📝 分页查询:内置分页支持
  • 🔧 更新构建器:类型安全的更新操作构建
  • 🔢 聚合函数:Sum/Max/Min/Avg,NULL 安全
  • ♻️ 软删除恢复:Restore/RestoreByCond 按主键或条件恢复
  • 🔄 分批处理:Chunk/FirstOrCreate/FirstOrUpdate 高级操作
  • ↕️ 原子增减:IncrBy/DecrBy 无竞态字段更新

快速开始

安装
go get github.com/yi-nanping/gplus@v0.8.0
基础用法
package main

import (
    "context"
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "github.com/yi-nanping/gplus"
)

// 定义用户模型
type User struct {
    ID       uint   `gorm:"primaryKey;column:id"`
    Name     string `gorm:"column:name"`
    Age      int    `gorm:"column:age"`
    Email    string `gorm:"column:email"`
    IsVip    bool   `gorm:"column:is_vip"`
}

func main() {
    // 初始化 GORM 数据库连接
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // 创建 Repository
    repo := gplus.NewRepository[int, User](db)

    ctx := context.Background()

    // 1. 创建用户
    user := &User{Name: "张三", Age: 25, Email: "zhangsan@example.com", IsVip: false}
    err = repo.Save(ctx, user)
    if err != nil {
        fmt.Printf("创建用户失败: %v\n", err)
    }

    // 2. 查询单个用户
    query, model := gplus.NewQuery[User](ctx)
    query.Eq(&model.Name, "张三")
    
    result, err := repo.GetOne(query)
    if err != nil {
        fmt.Printf("查询用户失败: %v\n", err)
    } else {
        fmt.Printf("查询结果: %+v\n", result)
    }

    // 3. 分页查询
    pageQuery, pageModel := gplus.NewQuery[User](ctx)
    pageQuery.Gt(&pageModel.Age, 18).Order(&pageModel.ID, false)
    
    results, total, err := repo.Page(pageQuery, false)
    if err != nil {
        fmt.Printf("分页查询失败: %v\n", err)
    } else {
        fmt.Printf("分页结果: 总计 %d 条, 当前页 %d 条\n", total, len(results))
    }

    // 4. 更新用户
    updater, updaterModel := gplus.NewUpdater[User](ctx)
    updater.Set(&updaterModel.Name, "李四").Eq(&updaterModel.ID, user.ID)
    
    affected, err := repo.UpdateByCond(updater)
    if err != nil {
        fmt.Printf("更新用户失败: %v\n", err)
    } else {
        fmt.Printf("更新成功,影响行数: %d\n", affected)
    }

    // 5. 删除用户
    rowsAffected, err := repo.DeleteById(ctx, user.ID)
    if err != nil {
        fmt.Printf("删除用户失败: %v\n", err)
    } else {
        fmt.Printf("删除成功,影响行数: %d\n", rowsAffected)
    }
}

核心功能

Repository 模式
// 创建 Repository
repo := gplus.NewRepository[uint, User](db)

// 写操作
repo.Save(ctx, &user)                          // 纯 INSERT(非 upsert)
repo.SaveBatch(ctx, users)                     // 批量 INSERT
repo.Upsert(ctx, &user)                        // insert-or-update(按主键)
repo.UpsertBatch(ctx, users)                   // 批量 upsert
repo.CreateBatch(ctx, ptrs, batchSize)         // 分批 INSERT
repo.UpdateById(ctx, &user)                    // 按主键更新非零字段
repo.UpdateByIds(ctx, ids, updater)            // 按主键列表批量更新
repo.UpdateByCond(updater)                     // 按条件批量更新
repo.IncrBy(updater, col, delta)               // 原子自增
repo.DecrBy(updater, col, delta)               // 原子自减
repo.DeleteById(ctx, 1)                        // 按主键删除
repo.DeleteByIds(ctx, []uint{1, 2, 3})         // 按主键列表批量删除
repo.InsertOnConflict(ctx, &user, oc)          // 单条带冲突处理插入
repo.InsertBatchOnConflict(ctx, users, oc)     // 批量带冲突处理插入
repo.Restore(ctx, id)                          // 按主键恢复软删除
repo.RestoreByCond(q)                          // 按条件批量恢复软删除

// 读操作
repo.GetById(ctx, 1)                           // 按主键查单条
repo.GetByIds(ctx, []uint{1, 2, 3})            // 按主键列表批量查询
repo.GetOne(query)                             // 按条件查单条
repo.Last(query)                               // 按主键倒序取第一条
repo.List(query)                               // 查询列表
repo.ListMap(query, func(u User) uint { return u.ID }) // 列表转 map
repo.Page(query, false)                        // 分页查询
repo.Count(query)                              // 计数
repo.Exists(query)                             // 判断是否存在
repo.FirstOrCreate(query, &User{Name: "张三"}) // 查找或创建
repo.FirstOrUpdate(query, updater, &User{})    // 查找或创建并更新
repo.Chunk(query, 100, fn)                     // 分批处理(主键游标)
类型安全查询构建器
// 创建查询
query, model := gplus.NewQuery[User](ctx)

// 条件构建
query.Eq(&model.Name, "张三")
      .Gt(&model.Age, 18)
      .In(&model.ID, []int{1, 2, 3})
      .Like(&model.Email, "%@example.com")
      .Order(&model.ID, false)
      .Limit(10)
      .Offset(0)

// 复杂条件(AND 嵌套块)
query.And(func(sub *gplus.Query[User]) {
    sub.Eq(&model.Age, 20).OrEq(&model.IsVip, true)
})

// 连接查询
query.LeftJoin("profiles", "users.id = profiles.user_id")

// 执行查询
results, err := repo.List(query)
更新构建器
// 创建更新器
updater, model := gplus.NewUpdater[User](ctx)

// 设置更新字段
updater.Set(&model.Name, "新名字")
       .Set(&model.Age, 30)
       .SetExpr(&model.Version, "version + ?", 1)

// 设置条件
updater.Eq(&model.ID, 1)
       .Gt(&model.Status, 0)

// 执行更新
affected, err := repo.UpdateByCond(updater) // 第二个参数是事务,传nil表示不用事务
事务支持
err := repo.Transaction(func(tx *gorm.DB) error {
    // 在事务中执行操作
    user1 := &User{Name: "用户1", Age: 20}
    if err := repo.SaveTx(ctx, user1, tx); err != nil {
        return err
    }

    user2 := &User{Name: "用户2", Age: 25}
    if err := repo.SaveTx(ctx, user2, tx); err != nil {
        return err
    }

    return nil
})
原生条件与排序
// WhereRaw:添加原生 SQL 条件(防注入:用 ? 占位符)
query.WhereRaw("YEAR(created_at) = ?", 2024)
query.WhereRaw("age > ? AND age < ?", 18, 60)

// OrderRaw:添加复杂排序表达式(与 Order 可混用,保留调用顺序)
query.OrderRaw("FIELD(status, 'active', 'pending', 'closed')")
query.OrderRaw("score DESC NULLS LAST") // PostgreSQL
query.Order(&model.CreatedAt, false).OrderRaw("FIELD(priority, 1, 2, 3)")
Upsert(insert-or-update)
// 无主键:执行 INSERT
// 有主键:执行 UPDATE(覆盖所有字段)
repo.Upsert(ctx, &user)
repo.UpsertBatch(ctx, users)

// 注意:Save/SaveBatch 是纯 INSERT,不会更新已有记录
// 如需只更新部分字段,使用 UpdateById 或 UpdateByCond
聚合函数
// Sum/Max/Min/Avg 为包级泛型函数,需显式传入 repo
// R 为返回类型(int64、float64 等),空表或无匹配时返回零值(NULL 安全)
q, m := gplus.NewQuery[User](ctx)
q.Gt(&m.Age, 18)

total, err := gplus.Sum[User, int64, uint](repo, q, &m.Age)
max, err   := gplus.Max[User, int64, uint](repo, q, &m.Age)
min, err   := gplus.Min[User, int64, uint](repo, q, &m.Age)
avg, err   := gplus.Avg[User, float64, uint](repo, q, &m.Age)
OnConflict(按唯一键 upsert)

InsertOnConflict / InsertBatchOnConflict 支持数据库原生冲突处理,覆盖四种策略:

_, m := repo.NewQuery(ctx)

// 1. 冲突时跳过(幂等写入)
repo.InsertOnConflict(ctx, &user, gplus.OnConflict{
    Columns:   []any{&m.Email},
    DoNothing: true,
})
// → INSERT INTO users(...) ON CONFLICT (email) DO NOTHING

// 2. 冲突时只更新指定列
repo.InsertOnConflict(ctx, &user, gplus.OnConflict{
    Columns:   []any{&m.Email},
    DoUpdates: []any{&m.Name, &m.UpdatedAt},
})
// → ON CONFLICT (email) DO UPDATE SET name=EXCLUDED.name, updated_at=EXCLUDED.updated_at

// 3. 冲突时覆盖除主键外所有列
repo.InsertOnConflict(ctx, &user, gplus.OnConflict{
    Columns:     []any{&m.Email},
    DoUpdateAll: true,
})

// 4. 冲突时原子表达式更新(批量累加计数器)
repo.InsertBatchOnConflict(ctx, stats, gplus.OnConflict{
    Columns:     []any{&m.UserID, &m.Date},
    UpdateExprs: map[string]any{"count": gorm.Expr("count + excluded.count")},
})
// → ON CONFLICT (user_id, date) DO UPDATE SET count = count + excluded.count

方言说明Columns 在 Postgres/SQLite 中必须指定;MySQL 按唯一索引自动判定,可省略。 UpdateExprs 中的表达式语法因数据库而异(MySQL 用 VALUES(col),Postgres/SQLite 用 excluded.col)。

软删除恢复
// 按主键恢复单条
affected, err := repo.Restore(ctx, 1)

// 按条件批量恢复(空条件返回 ErrRestoreEmpty)
q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.Status, "deleted")
affected, err := repo.RestoreByCond(q)
分批处理
// 主键游标分批(非 OFFSET),批次内顺序稳定
q, _ := gplus.NewQuery[User](ctx)
err := repo.Chunk(q, 100, func(batch []User) error {
    for _, u := range batch {
        // 处理每条记录
    }
    return nil
})
查找或创建 / 查找或更新
// FirstOrCreate:找到则返回,找不到则用 defaults 创建
q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.Email, "test@example.com")
user, created, err := repo.FirstOrCreate(q, &User{Name: "新用户", Email: "test@example.com"})

// FirstOrUpdate:找到则按 updater 更新,找不到则用 defaults 创建
u, um := gplus.NewUpdater[User](ctx)
u.Set(&um.Name, "更新名字")
user, created, err := repo.FirstOrUpdate(q, u, &User{Name: "新用户"})
原子增减
// 对 score 字段原子 +10,附带 WHERE 条件
u, m := gplus.NewUpdater[User](ctx)
u.Eq(&m.ID, 1)
affected, err := repo.IncrBy(u, &m.Score, 10)
affected, err  = repo.DecrBy(u, &m.Score, 5)
ListMap(列表转 map)
// 查询结果直接转换为 map,key 由回调函数决定
q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.IsVip, true)
userMap, err := repo.ListMap(q, func(u User) uint { return u.ID })
// userMap 类型为 map[uint]User,可按 ID 直接取值
user := userMap[42]
Pluck(提取单列)
// 提取单列值,返回指定类型的切片
q, m := gplus.NewQuery[User](ctx)
q.Gt(&m.Age, 18).Order(&m.ID, true)
names, err := gplus.Pluck[User, string, uint](repo, q, &m.Name)
// names 类型为 []string
FindAs / FindOneAs — 投影查询(Query-chain-safe)

走 GORM Query callback chain,下游挂在 Query chain 上的隔离/审计 callback 会触发。

type UserVO struct {
    Name     string  // 默认匹配 SELECT 列 `name`(snake_case)
    DeptName string  // 匹配 `dept_name`(必须 alias,否则字段名冲突时无法定位)
}

// 多行
var rows []UserVO
q, _ := gplus.NewQuery[User](ctx)
q.LeftJoin("dept", "users.dept_id = dept.id").
    Select("users.name", "dept.name AS dept_name")
err := gplus.FindAs(repo, q, &rows)

// 单行(无匹配返回 gorm.ErrRecordNotFound)
var one UserVO
q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.ID, 1)
err := gplus.FindOneAs(repo, q, &one)

// 事务
err := db.Transaction(func(tx *gorm.DB) error {
    q, m := gplus.NewQuery[User](ctx)
    q.Eq(&m.Status, "active")
    return gplus.FindAsTx(repo, q, &rows, tx)
})

主表 vs 结果结构心智模型

  • Repository[D, T] 绑定主表 schema(T)+ 提供 dbResolver / ctx 注入
  • Dest 仅决定 SELECT 列 → struct 字段映射(GORM 默认 snake_case)
  • 跨表 JOIN 列必须 SQL alias,否则字段名冲突 GORM 无法定位
数据权限(DataRule)

DataRule 通过 context.Context 传入,由 Repository 方法自动应用到所有查询和写操作,无需在每处手动添加条件。适合多租户、行级权限等场景。

// 定义数据权限规则(通常在中间件中设置)
rules := []gplus.DataRule{
    {Column: "tenant_id", Op: gplus.OpEq, Val: "tenant-abc"},
    {Column: "deleted_at", Op: gplus.OpIsNull},
}
ctx = context.WithValue(ctx, gplus.DataRuleKey, rules)

// 之后所有使用该 ctx 的查询都会自动附加上述条件
q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.IsVip, true)
// 实际执行:WHERE is_vip = true AND tenant_id = 'tenant-abc' AND deleted_at IS NULL
users, err := repo.List(q)

注意DataRule.Column 仅支持字母/数字/下划线/点,含括号或运算符的表达式会被拒绝以防注入。

悲观锁查询(GetByLock)

GetByLock 必须在事务中使用,否则返回 ErrTransactionReq

err := repo.Transaction(func(tx *gorm.DB) error {
    q, m := gplus.NewQuery[User](ctx)
    q.Eq(&m.ID, 1).LockForUpdate() // FOR UPDATE
    user, err := repo.GetByLock(ctx, q, tx)
    if err != nil {
        return err
    }
    // 在锁持有期间更新数据
    u, um := gplus.NewUpdater[User](ctx)
    u.Set(&um.Score, user.Score+10).Eq(&um.ID, user.ID)
    _, err = repo.UpdateByCondTx(u, tx)
    return err
})
便捷构建器(repo.NewQuery / repo.NewUpdater)

从 v0.3.0 起,Repository 提供 NewQuery/NewUpdater 方法,无需重复指定泛型参数:

repo := gplus.NewRepository[uint, User](db)

// 等价于 gplus.NewQuery[User](ctx),类型由 repo 自动推导
q, m := repo.NewQuery(ctx)
q.Eq(&m.IsVip, true)
users, err := repo.List(q)

// 等价于 gplus.NewUpdater[User](ctx)
u, um := repo.NewUpdater(ctx)
u.Set(&um.Name, "新名字").Eq(&um.ID, 1)
affected, err := repo.UpdateByCond(u)
自定义 Scope 注入
// WithScope 注入任意 GORM scope,支持 Query 和 Updater
q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.IsVip, true).
    WithScope(func(db *gorm.DB) *gorm.DB {
        return db.Scopes(myTenantScope)
    })
原生 SQL 查询
// 查询原始 SQL
users, err := repo.RawQuery(ctx, "SELECT * FROM users WHERE age > ?", 18)

// 执行原始 SQL
affected, err := repo.RawExec(ctx, "UPDATE users SET status = ? WHERE id = ?", 1, 123)

// 查询到自定义结构
var userStats []UserStats
err = repo.RawScan(ctx, &userStats, "SELECT name, COUNT(*) as count FROM users GROUP BY name")

条件操作符

GPlus 支持丰富的条件操作符,所有操作符都在 consts.go 中定义:

方法名 操作符 常量 说明
Eq() = OpEq 等于
Ne() <> OpNe 不等于
Gt() > OpGt 大于
Ge() >= OpGe 大于等于
Lt() < OpLt 小于
Le() <= OpLe 小于等于
Like() LIKE OpLike 模糊匹配
NotLike() NOT LIKE OpNotLike 非模糊匹配
In() IN OpIn 包含
NotIn() NOT IN OpNotIn 不包含
IsNull() IS NULL OpIsNull 为空
IsNotNull() IS NOT NULL OpIsNotNull 不为空
Between() BETWEEN OpBetween 在范围内
NotBetween() NOT BETWEEN OpNotBetween 不在范围内
LikeLeft() LIKE - 左模糊查询(自动补 %val
LikeRight() LIKE - 右模糊查询(自动补 val%
OrEq() = - OR 等于
OrNe() <> - OR 不等于
OrGt() > - OR 大于
OrGe() >= - OR 大于等于
OrLt() < - OR 小于
OrLe() <= - OR 小于等于
OrLike() LIKE - OR 模糊匹配
OrIn() IN - OR 包含
OrIsNull() IS NULL - OR 为空
OrIsNotNull() IS NOT NULL - OR 不为空

连接操作

支持多种连接类型,所有连接类型常量在 consts.go 中定义:

方法名 常量 说明
LeftJoin() JoinLeft 左连接:返回左表所有记录,即使右表无匹配
RightJoin() JoinRight 右连接:返回右表所有记录,即使左表无匹配
InnerJoin() JoinInner 内连接:仅返回两个表中匹配的记录(交集)
OuterJoin() JoinOuter 裸 OUTER JOIN(非标准 SQL,多数数据库不支持,建议用 FullJoin
FullJoin() JoinFull 全外连接:返回左右表中所有的记录
CrossJoin() JoinCross 交叉连接:返回笛卡尔积
NaturalJoin() JoinNatural 自然连接:基于相同列名自动匹配

使用示例:

// 左连接
query.LeftJoin("dept", "user.dept_id = dept.id")

// 内连接  
query.InnerJoin("role", "user.role_id = role.id")

// 右连接
query.RightJoin("profile", "user.id = profile.user_id")

// 交叉连接
query.CrossJoin("settings")

// 自然连接
query.NaturalJoin("user_settings")

错误变量

变量 触发时机
ErrQueryNil 传入 nil 的 Query/Updater
ErrRawSQLEmpty RawQuery/RawExec/RawScan 传入空字符串
ErrDeleteEmpty DeleteByCondTx 无条件且未调用 Unscoped()
ErrUpdateEmpty UpdateByCond 没有设置任何字段
ErrUpdateNoCondition UpdateByCond 有字段但没有 WHERE 条件
ErrTransactionReq GetByLock 未在事务中调用
ErrDefaultsNil FirstOrCreate/FirstOrUpdate 传入 nil defaults
ErrRestoreEmpty RestoreByCond/RestoreByCondTx 无条件

集成方式

方式一:直接内嵌 Repository(推荐)

gplus.Repository 内嵌到你的业务 Repository 结构体中,即可直接使用所有 CRUD 方法,同时可以在结构体上添加自定义业务方法。

type UserRepository struct {
    gplus.Repository[uint, User]
}

func NewUserRepository(db *gorm.DB) *UserRepository {
    return &UserRepository{
        Repository: gplus.NewRepository[uint, User](db),
    }
}

// 添加自定义业务方法
func (r *UserRepository) FindActiveVips(ctx context.Context) ([]User, error) {
    q, m := gplus.NewQuery[User](ctx)
    q.Eq(&m.IsVip, true).Eq(&m.Status, "active")
    return r.List(q)
}

使用时:

repo := NewUserRepository(db)

// 直接使用内嵌的通用方法
user, err := repo.GetById(ctx, 1)

// 使用自定义业务方法
vips, err := repo.FindActiveVips(ctx)
方式二:依赖注入(适合 DI 框架)

*gplus.Repository 作为字段注入,适合 Wire、Fx 等依赖注入框架。

type UserService struct {
    userRepo *gplus.Repository[uint, User]
    orderRepo *gplus.Repository[uint, Order]
}

func NewUserService(
    userRepo *gplus.Repository[uint, User],
    orderRepo *gplus.Repository[uint, Order],
) *UserService {
    return &UserService{
        userRepo:  userRepo,
        orderRepo: orderRepo,
    }
}

func (s *UserService) GetUserOrders(ctx context.Context, userID uint) ([]Order, error) {
    q, m := gplus.NewQuery[Order](ctx)
    q.Eq(&m.UserID, userID).Order("created_at DESC")
    return s.orderRepo.List(q)
}
方式三:全局单例(简单项目)

适合小型项目或脚本,直接在包级别声明 Repository 变量。

var (
    UserRepo  = gplus.NewRepository[uint, User](db)
    OrderRepo = gplus.NewRepository[uint, Order](db)
)

项目结构

gplus/
├── builder.go      # 查询构建器核心
├── consts.go       # 常量定义
├── query.go        # 查询构建器
├── repository.go   # Repository 模式实现
├── schema.go       # 模型结构解析
├── update.go       # 更新构建器
├── utils.go        # 工具函数
├── go.mod          # Go 模块定义
└── go.sum          # 依赖校验

性能优化

  • 智能缓存:模型结构解析结果缓存,减少反射开销
  • 预分配内存:查询条件切片预分配,减少内存分配次数
  • 零分配设计:关键路径避免内存分配

依赖

  • Go 1.24+
  • GORM v1.31.1+

方言支持

数据库 状态 CI 验证 备注
SQLite ✅ 完整 :memory: 默认开发与单元测试方言;getQuoteChar 返回 "
MySQL 8.0+ ✅ 完整 mysql:8.0 service getQuoteChar 返回 `;ON CONFLICT 用 VALUES(col) 表达式
PostgreSQL 16+ ✅ 完整 postgres:16 service getQuoteChar 返回 ";ON CONFLICT 用 excluded.col 表达式
Oracle 12c+ ⚠️ build tag ✗ 不在 CI(启动慢) go test -tags=oracle 跑;getQuoteChar 返回空 quoter(避免 ORA-00904,详见已知陷阱)
DM 8 (Oracle 兼容) ⚠️ build tag ✗ 不在 CI(镜像大) go test -tags=dm 跑;getQuoteChar 返回双引号(与 postgres 一致,dameng migrator 引号 lowercase 建表)
SQL Server ⚠️ 部分 getQuoteChar 返回 [ ];未在 CI 验证,alias 体系未实测
TiDB ⚠️ 别名走 MySQL 分支 getQuoteChar 返回反引号同 MySQL;未在 CI 验证

已知方言差异(详见"已知陷阱"章节):

  • MySQL 1093:UPDATE 目标表不能与子查询 FROM 同表
  • PG 42702:ON CONFLICT DO UPDATE 中裸列名 + EXCLUDED 同名时视为歧义,须用表名限定
  • PG 严格 SQL:HAVING 不可引用 SELECT 列别名,须重复聚合表达式
  • LIKE 大小写敏感性:MySQL 默认 utf8mb4_general_ci 不敏感、PG 默认敏感、SQLite 默认不敏感
  • 占位符:MySQL/SQLite 用 ?,PG 用 $N(驱动统一处理,库代码方言无关)
  • Oracle 限制(详见 spec docs/superpowers/specs/2026-05-07-oracle-support-design.md):
    • gplus.getQuoteChar 返回空 quoter——godoes/gorm-oracle migrator 用 UPPERCASE 不带引号建表,加双引号会触发 ORA-00904;列名是 Oracle 保留字(order/size/level)时需用户手动加引号
    • '' 自动转 NULL(致命差异,影响 IsNull / Empty 判断)
    • 输出列名默认 UPPERCASE:RawScan 映射小写 struct tag 时需 SQL 显式 AS "col" 锁定 lowercase
    • CLOB/TEXT 字段不能直接 WHERE,所有 string 字段须显式 gorm:"size:N" 约束
    • NULLS LAST 排序默认(与 PG 升序相反)
    • RETURNING 仅支持单行(影响 SaveBatch/UpsertBatch,本期 t.Skip)
    • 标识符长度 30/128 字符上限
    • 不支持 ON CONFLICT(用 MERGE INTO)
  • DM 8 限制(Oracle 兼容模式,详见 spec docs/superpowers/specs/2026-05-08-dm-support-design.md 与下方"DM 数据库支持"章节):
    • gplus.getQuoteChar 返回双引号——与 Oracle 不共用空 quoter:godoes/gorm-dameng migrator 实测用引号 lowercase 建表(CREATE TABLE "username" ...),列存为 case-sensitive 小写,DM CASE_SENSITIVE=Y 下裸标识符会被 UPPERCASE 解析触发 Error -2111 无效的列名,必须用双引号锁定小写
    • 继承 Oracle 兼容模式全部限制:''=NULL / 输出 UPPERCASE / CLOB WHERE / NULLS LAST / RETURNING 单行 / 标识符长度 / 无 ON CONFLICT
    • DM 特有:COMPATIBLE_MODE=2 必须显式开启(docker run 加 -e COMPATIBLE_MODE=2SELECT PARA_VALUE FROM V$DM_INI WHERE PARA_NAME='COMPATIBLE_MODE' 应返回 2)
    • 镜像默认密码版本差异:dameng 历史镜像有 SYSDBA / SYSDBA001 / 首登强制改密——以拉到的镜像 README 为准

DM 数据库支持(v0.8.3+)

仅本地/CI 验证场景。生产侧使用见下方"下游生产侧集成"。

1. Quickstart 5 步
  1. 拉 gplusgo get github.com/yi-nanping/gplus@v0.8.3
  2. 拉 DM 8 镜像:从 dameng 技术社区 下载 DM 8 docker tar 包,docker loaddocker run 启动(参见下方"启动 DM 8 容器")
  3. 设 DSN 环境变量export TEST_DM_DSN="dm://SYSDBA:<密码>@127.0.0.1:5236"(密码以镜像 README 为准)
  4. 跑测试go test -tags=dm -v ./...(强制不漏跑:TEST_DM_REQUIRED=1 go test -tags=dm ./... —— DSN 不通时 t.Fatalf 而非 t.Skip
  5. 遇错查表:见下方"错误码导航"
2. TEST_DM_DSN 格式

BNF:

TEST_DM_DSN := "dm://" <user> ":" <password> "@" <host> ":" <port> [ "/" <schema> ] [ "?" <params> ]

样例:

# 本地 docker 默认实例(密码以镜像 README 为准,常见 SYSDBA / SYSDBA001 / 自定义)
export TEST_DM_DSN="dm://SYSDBA:<密码>@127.0.0.1:5236"

# 指定 schema 切换
export TEST_DM_DSN="dm://SYSDBA:<密码>@127.0.0.1:5236/MYSCHEMA"

# 字符集参数(dameng 驱动支持的连接参数见 godoes/gorm-dameng README)
export TEST_DM_DSN="dm://SYSDBA:<密码>@127.0.0.1:5236?charset=utf8"
3. 下游生产侧集成
import (
    dameng "github.com/godoes/gorm-dameng"
    "gorm.io/gorm"
    "github.com/yi-nanping/gplus"
)

func main() {
    db, _ := gorm.Open(dameng.Open("dm://SYSDBA:..."), &gorm.Config{})
    repo := gplus.NewRepository[int64, User](db)
    // ... 与 sqlite/mysql/pg 完全一样
}

gplus 自身不预先注册 Dialector,下游需自己 import _ "github.com/godoes/gorm-dameng"(或显式 gorm.Open(dameng.Open(...)))。

4. quoter 策略与列名匹配(重要)

DM 8 走双引号 quoter(与 PostgreSQL/SQLite 一致),不与 Oracle 共用空 quoter——这是 v0.8.3 实施期实测推翻 spec 早期假设后的最终决策:

  • godoes/gorm-dameng v0.7.2 migrator 用 CREATE TABLE "my_sql_users" ("username" VARCHAR(64),...) 引号 lowercase 建表,列名在 DM 中存为 case-sensitive 小写
  • DM CASE_SENSITIVE=Y + Oracle 兼容模式下,裸标识符 username 会被 UPPERCASE 解析为 USERNAME,与 DB 内的小写实际列名不匹配,触发 Error -2111 无效的列名
  • gplus 在 dm 方言下用 "/" 自动给列名加引号(gorm-dameng Dialector.QuoteTo 也会自动加引号),SELECT/UPDATE/DELETE 路径下匹配 case-sensitive 小写

下游手写 RawSQL/WhereRaw 时也需注意:列名必须用双引号包裹保持小写(WHERE "username" = ?),否则 DM 仍会 UPPERCASE 解析失败。

5. 保留字 → 措施对照表

DM 8 Oracle 兼容模式继承 Oracle 全部保留字(order / size / level / comment / type / group / role / number / date 等)。即使走双引号 quoter,下游遇到保留字 列名时仍按优先级处理:

优先级 措施 示例
1(推荐) 改 struct tag column: 避开 Order int gorm:"column:ord_no"
2 用 RawSQL/WhereRaw 加双引号 q.WhereRaw(\"order" = ?`, 100)`
3 等 v1.0+ 保留字自动检测能力(参见 TD-14)
6. 错误码导航
错误码 触发场景 措施
Error -2111 无效的列名 裸标识符大小写不匹配(如 username 被 UPPERCASE 解析为 USERNAME 用双引号锁定小写,或检查 getQuoteChar 返回的 quoter 是否是双引号(v0.8.3+)
ORA-00932 等价(数据类型不一致) string 长字段映射 CLOB 后 LIKE/IN struct 字段加 gorm:"size:N" 显式约束
ORA-01430 等价(列已存在) migrator 重复 ALTER ADD setup 走 DROP+AutoMigrate 路径,参考 dm_setup_test.gotruncateDMTables
网络通信异常 / connect failed DSN 密码不对 / 端口不通 镜像默认密码以 README 为准;首登强制改密版本需先 ALTER USER SYSDBA IDENTIFIED BY ...
7. 验证 COMPATIBLE_MODE=2 生效
-- 容器内 disql 执行
SELECT PARA_VALUE FROM V$DM_INI WHERE PARA_NAME='COMPATIBLE_MODE';
-- 应返回 2(Oracle 兼容);若不是 2,docker run 加 -e COMPATIBLE_MODE=2 重建
8. 未验证场景兜底声明

v0.8.3 仅验证:DM 8 Oracle 兼容模式(COMPATIBLE_MODE=2)+ 单实例 + UTF-8 + 开发环境。未验证(下游需自行验证或避开):

  • 国密 SM3/SM4 加密列
  • Kerberos 认证
  • DSC 集群 / 读写分离 / 容灾双活
  • DM 7 及更老版本(spec §1.2 排除)
  • DM MySQL/PG/TD 兼容模式(v0.8.4+ 候选;切换后 quoter 策略需重测)
启动 DM 8 容器(WSL2 + Docker Engine)
# 加载 dameng 技术社区 tar 包(或自构建镜像)
wsl -d Ubuntu-24.04 -e docker load -i /mnt/d/downloads/dm8.tar

# 启动(单行,避免续行符不透传)
wsl -d Ubuntu-24.04 -e docker run -d --name dm8 -p 5236:5236 \
  -e INSTANCE_NAME=DM8TEST -e PAGE_SIZE=16 -e UNICODE_FLAG=1 \
  -e CASE_SENSITIVE=Y -e COMPATIBLE_MODE=2 <image_tag>
GOPROXY 配置(一般性建议)

国内开发者拉取依赖推荐 GOPROXY 镜像加速(与 DM 支持无特定关系,gplus 通用):

go env -w GOPROXY=https://goproxy.cn,direct

:spec 早期版本曾假设 godoes/gorm-dameng 通过 transitive 引入 gitee.com/chunanyong/dm,故强调 GOPRIVATE fallback。plan 阶段 Task 0 实测推翻此假设——godoes/gorm-dameng v0.7.2 driver 实现自带在子包 dm8/i18n/parser/security/util,所有依赖在 github.com 与 golang.org 上,标准 GOPROXY 即可。

贡献

欢迎提交 Issue 和 Pull Request!

版本历史

v0.6.0(最新,2026-04-30)

类型安全子查询,消灭体系性 WhereRaw 裂缝。

// 1. WHERE id IN (SELECT user_id FROM orders WHERE status='paid')
paidUserIDs, order := gplus.NewQuery[Order](ctx)
paidUserIDs.Select(&order.UserID).Eq(&order.Status, "paid")

q, user := gplus.NewQuery[User](ctx)
q.InSub(&user.ID, paidUserIDs).Order(&user.CreatedAt, false)
result, _ := repo.List(q)

// 2. WHERE age > (SELECT AVG(age) FROM users)
avgAge, _ := gplus.NewQuery[User](ctx)
avgAge.SelectRaw("AVG(age)")

q2, user2 := gplus.NewQuery[User](ctx)
q2.GtSub(&user2.Age, avgAge)

// 3. UPDATE users SET status='inactive' WHERE id IN (SELECT user_id FROM orders WHERE last_order < cutoff)
inactiveOrders, order2 := gplus.NewQuery[Order](ctx)
inactiveOrders.Select(&order2.UserID).Lt(&order2.LastOrderAt, cutoff)

u, user3 := gplus.NewUpdater[User](ctx)
u.Set(&user3.Status, "inactive").InSub(&user3.ID, inactiveOrders)
repo.UpdateByCond(u)

新增 32 方法(Query 16 + Updater 16) + Subquerier 接口 + ErrSubqueryNil sentinel + SelectRaw

⚠️ 延迟调用语义sub 传入后仍可被修改,修改会反映到最终 SQL。推荐 sub 构建完成后再传入。

详见 CHANGELOG v0.6.0 章节和 spec 文档。

v0.5.1(2026-04-30)

安全修复:

  • DataRule 接入 7 条 by-ID 路径:补 v0.2.0 by-Cond 修复后剩余的系统性遗漏(GetById/GetByIds/UpdateById/UpdateByIds/DeleteById/DeleteByIds/Restore,含 Tx 变体共 14 条调用),杜绝跨租户读 / 改 / 删 / 恢复风险
  • ToUpdateSQL(nil) 错误类型由 ErrQueryNil 改为 fmt.Errorf("%w: %w", ErrUpdateEmpty, ErrQueryNil) 双 wrap,errors.Is 双向兼容旧调用方

⚠️ 行为变更:跨租户场景 affected 由 >0 变 =0;启用 DataRule 时 UpdateById 返回的 ErrOptimisticLock 可能由 DataRule 拦截而非版本冲突所致,调用方不应无条件重试。详见 CHANGELOG。

v0.5.0(2026-04-24)

新增功能:

  • 乐观锁:模型字段标注 gplus:"version" 即自动启用,UpdateById / UpdateByIdTx 自动追加 WHERE version = oldVerSET version = version + 1,affected==0 时返回 ErrOptimisticLock,更新成功后 entity.Version 自动回写
    • 支持 int / int32 / int64 / uint / uint32 / uint64,含嵌入字段中的 version
    • 无 version 字段的模型保持原有路径,零额外开销
  • 新增 ErrOptimisticLock 哨兵错误
v0.4.0(2026-04-23)

新增功能:

  • OnConflict upsert:InsertOnConflict / InsertBatchOnConflict(含 Tx 变体),支持四种策略:
    • DoNothing(冲突跳过)、DoUpdates(按 EXCLUDED 覆盖指定列)、DoUpdateAll(覆盖除主键外所有列)、UpdateExprs(自定义表达式,可与 DoUpdates 组合)
  • 调试 SQLQuery.ToSQL / Query.ToCountSQL / Updater.ToSQL(含 Repository 同名便捷方法),基于 GORM DryRun 模式输出参数已内联的 SQL
  • 新增 ErrOnConflictInvalid 哨兵错误

Bug 修复:

  • Distinct + Page 时 COUNT 路径未应用 DISTINCT 子查询,导致 total 虚高
  • FirstOrUpdate 重读改用主键精确查找,避免更新查询条件字段时按旧字段找不到新记录
v0.3.2

Bug 修复:

  • 修复 PluckTx 未能在 GORM clause 定型前提前应用 Distinct,导致 Distinct 标志丢失
v0.3.1

新增功能:

  • Repository 新增 NewQuery/NewUpdater 便捷方法,无需重复指定泛型参数

Bug 修复:

  • 修复 ToDB 未使用 Session{NewDB: true},导致继承"脏" db 上已有的条件
v0.3.0

新增功能:

  • 新增 IncrBy/DecrBy(含 Tx 变体):原子字段自增自减
  • 新增 WithScope:向 Query/Updater 注入自定义 GORM scope
  • 新增 Last/LastTx:按主键倒序取第一条记录
  • 新增 Restore/RestoreTx:按主键恢复软删除记录
  • 新增 RestoreByCond/RestoreByCondTx:按条件批量恢复软删除
  • 新增 ListMap/ListMapTx:查询结果转换为 map[D]T
  • 新增 Sum/Max/Min/Avg(含 Tx 变体):聚合函数,NULL 安全
  • 新增 Chunk/ChunkTx:主键游标分批处理
  • 新增 FirstOrCreate:原子查找或创建
  • 新增 FirstOrUpdate:原子查找或创建并更新
  • 新增 UpdateByIds/UpdateByIdsTx:按主键列表批量更新
  • 新增 DeleteByIds/DeleteByIdsTx:按主键列表批量删除
  • 新增 GetByIds/GetByIdsTx:按主键列表批量查询
  • 新增 IsEmpty():判断 Query/Updater 是否无条件(WithScope 不计入)
  • 新增错误变量 ErrDefaultsNilErrRestoreEmpty
  • 新增 .gitattributes:统一行尾为 LF
v0.2.1(2026-03-20)

Bug 修复:

  • 修复 applyGroupHaving 两处实现缺陷:OrHaving 条件错误追加到 WHERE 而非 HAVING;HavingGroup OR 嵌套分组未正确构建 clause 树
  • 修复 Query[T].Clear() 未重置 errsdataRuleApplied,导致复用同一 Query 实例时状态泄漏
  • 修复 DataRule.Column 缺少白名单校验,含括号/运算符的恶意表达式可绕过 quoteColumn 转义

测试:

  • 覆盖率从 93.3% 提升至 94.0%
  • 新增 TestQuery_SQL 综合 DryRun SQL 验证测试(20 个子测试)
v0.2.0
  • 泛型 Repository、Query Builder、Updater 初始版本
  • 支持 DataRule 数据权限、软删除、悲观锁、预加载等特性

已知陷阱

q.ToDB(db).Scan / .Row / .Rows 绕过 Query callback chain(CRITICAL)

GORM v1.31.1 中 db.Scan / db.Row / db.Rows 内部走 Row callback chain,不会触发挂在 Query chain 上的下游 callback(数据隔离 / 审计 / 查询日志)。在依赖这些 callback 的项目中,使用上述三种调用会导致跨租户数据泄露 / 审计日志缺失

必须改用 FindAs / FindOneAs

旧写法(漏洞) 新写法(安全)
q.ToDB(db).Model(&T{}).Scan(&rows) gplus.FindAs(repo, q, &rows)
q.ToDB(db).Model(&T{}).Limit(1).Scan(&one) gplus.FindOneAs(repo, q, &one)

排查老代码:见 CHANGELOG v0.7.0 行为约束段(含两条互补 grep 命令)

RawQuery / RawScan / RawScanTx 的 Schema=nil 问题

Raw SQL 路径 Schema=nil,下游 callback 在正确实现下应短路(if Schema == nil { return })。若下游 callback 未判断 Schema == nil,行为不可预测。涉及敏感数据必须在 SQL 中手写 WHERE。

DataRule + JOIN 列二义性

DataRule.Column 在多表 JOIN 下若无表前缀(如 "dept_id"),可能产生 SQL 二义性错误(MySQL 报 ambiguous)或静默走错表。必须用 table.col 形式(如 "users.dept_id")。

col 字符串形式不验证

Sum/Max/Min/Avg/Pluck 接受字符串列名时,gplus 不做白名单校验(与 DataRule.Column 不同)。禁止将用户输入直接传入 col

MySQL 限制:UPDATE 目标表不能与子查询 FROM 同表(错误 1093)

MySQL 不允许 UPDATE T ... WHERE col > (SELECT ... FROM T) —— 报 Error 1093 (HY000): You can't specify target table 'T' for update in FROM clause。SQLite / PostgreSQL 无此限制。

适用场景InSub / NotInSub / GtSub / LtSub / EqSub 等 16 个 Updater 子查询方法在 MySQL 下,若子查询源表与 UPDATE 目标表相同会触发该错误。

workaround:用 derived table 包一层让 MySQL 视为不同表

// ❌ MySQL 1093
avgQ, _ := gplus.NewQuery[User](ctx)
avgQ.SelectRaw("AVG(age)")
u, m := gplus.NewUpdater[User](ctx)
u.Set(&m.Name, "Senior").GtSub(&m.Age, avgQ)

// ✅ 用原生 SQL 包 derived table
u.Set(&m.Name, "Senior").
    WhereRaw("age > (SELECT avg_age FROM (SELECT AVG(age) AS avg_age FROM users) AS t)")

Alias 与跨表查询(v0.8.0)

类型安全的跨表列引用、自连接、相关 EXISTS 子查询。

跨表列引用
q, u := gplus.NewQuery[User](ctx)
o := gplus.As[Order](q, "o")
q.LeftJoinAs(o, &o.UserID, &u.ID, "").
    Eq(&o.Amount, 100)
// SQL: SELECT users.* FROM users
//      LEFT JOIN orders AS o ON o.user_id = users.id
//      WHERE o.amount = 100
同表自连接
q, u := gplus.NewQueryAs[User](ctx, "u")
boss := gplus.As[User](q, "boss")
q.LeftJoinAs(boss, &u.BossID, &boss.ID, "")
Correlated EXISTS
q, u := gplus.NewQuery[User](ctx)
sub, o := gplus.SubQuery[Order](q)
sub.Eq(&o.UserID, u.ID)
q.Exists(sub)
⚠️ DataRule × alias 安全契约

DataRule 不会自动应用到 alias 副表。 副表敏感字段(tenant_id 等)必须在 JoinAs 的 extraSQL 显式过滤:

q.LeftJoinAs(o, &o.UserID, &u.ID,
    "AND o.tenant_id = ?", tenantID) // ← 显式 + 参数化

禁止用 fmt.Sprintf 拼接用户输入到 extraSQL(SQL 注入)。禁止在 DataRule.Column 写 alias 前缀(如 "o.tenant_id")—— v0.9 cross-table DataRule 通过新增 DataRule.Table 字段提供,提前写 alias 前缀会形成兼容性陷阱。

许可证

MIT License

Documentation

Overview

alias.go - v0.8.0 alias 体系核心类型与函数

Package gplus 是基于 GORM 的泛型增强库,提供类型安全的查询构建器、 Repository 模式和数据权限规则注入等功能。

快速开始

定义模型并创建 Repository:

type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string `gorm:"column:name"`
    Email string `gorm:"column:email"`
    Age   int    `gorm:"column:age"`
}

repo := gplus.NewRepository[uint, User](db)

查询构建

NewQuery 返回一个类型安全的查询构建器和模型单例指针。 字段指针必须来自该返回的 *T 实例:

q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.Name, "Alice").Gt(&m.Age, 18).Order(&m.ID, false)
users, err := repo.List(q)

更新构建

u, m := gplus.NewUpdater[User](ctx)
u.Eq(&m.ID, 1).Set(&m.Name, "Bob")
affected, err := repo.UpdateByCond(u)

分页

q, _ := gplus.NewQuery[User](ctx)
list, total, err := repo.Page(q.Limit(10).Offset(0), false)

聚合

q, m := gplus.NewQuery[User](ctx)
q.Gt(&m.Age, 0)
avg, err := gplus.Avg[User, float64, uint](repo, q, &m.Age)

事务

err := db.Transaction(func(tx *gorm.DB) error {
    _, err := repo.SaveTx(ctx, &user, tx)
    return err
})

数据权限

通过 Context 注入 DataRule,对所有查询和写操作透明生效:

rules := []gplus.DataRule{{Column: "tenant_id", Value: 42}}
ctx = context.WithValue(ctx, gplus.DataRuleKey, rules)

调试

使用 ToSQL / ToCountSQL / ToUpdateSQL 获取最终执行的 SQL,无需实际执行:

q, m := gplus.NewQuery[User](ctx)
q.Eq(&m.Name, "Alice")
sql, args, err := gplus.ToSQL[uint, User](repo, q)

Alias 体系(v0.8.0)

通过 As[X](q, name) 创建 alias 实例(独立于规范单例的 *X), 支持跨表列引用 / 自连接 / correlated EXISTS。

详见 docs/superpowers/specs/2026-05-06-alias-system-design.md。

Package gplus — Query-chain-safe 投影查询 API

本文件提供 FindAs / FindOneAs / FindAsTx / FindOneAsTx 四个包级泛型函数, 让用户能写"投影查询走 GORM Query callback chain",避开 db.Scan / db.Row / db.Rows 绕过 Query chain 的隐患(导致下游 isolation/审计 callback 不触发)。

详见 docs/superpowers/specs/2026-05-01-scan-callback-fix-design.md

Index

Examples

Constants

View Source
const (
	OpEq         = "="
	OpNe         = "<>"
	OpGt         = ">"
	OpGe         = ">="
	OpLt         = "<"
	OpLe         = "<="
	OpLike       = "LIKE"
	OpNotLike    = "NOT LIKE"
	OpIn         = "IN"
	OpNotIn      = "NOT IN"
	OpIsNull     = "IS NULL"
	OpIsNotNull  = "IS NOT NULL"
	OpBetween    = "BETWEEN"
	OpNotBetween = "NOT BETWEEN"

	KeyAnd  = "AND"
	KeyOr   = "OR"
	KeyDesc = "DESC"
	KeyAsc  = "ASC"

	// JoinLeft 左连接 返回左表中的所有记录,即使右表中没有匹配的记录(保留左表)。
	JoinLeft = "LEFT JOIN"
	// JoinRight 右连接 返回右表中的所有记录,即使左表中没有匹配的记录(保留右表)。
	JoinRight = "RIGHT JOIN"
	// JoinInner 内连接 只返回两个表中都存在的记录 (交集)。
	JoinInner = "INNER JOIN"
	// JoinOuter 注意:裸 "OUTER JOIN" 不是标准 SQL,MySQL/PostgreSQL/SQLite 均不支持,
	// 调用此常量将导致数据库语法错误。如需外连接,请使用 JoinFull ("FULL OUTER JOIN")。
	JoinOuter = "OUTER JOIN"
	// JoinNatural 自然连接 返回两个表中相同列名和数据类型的所有记录。
	JoinNatural = "NATURAL JOIN"
	// JoinFull 全连接 返回左表中的所有记录,即使右表中没有匹配的记录。
	JoinFull = "FULL OUTER JOIN"
	// JoinCross 交叉连接 返回两个表中的所有记录,不论它们是否匹配。
	// 因为其就是把表A和表B的数据进行一个N*M的组合,即笛卡尔积。
	// 表达式如下:SELECT * FROM TableA CROSS JOIN TableB
	// 这个笛卡尔乘积会产生 4 x 4 = 16 条记录
	JoinCross = "CROSS JOIN"
)

Variables

View Source
var (
	ErrAliasDuplicate        = errors.New("gplus: alias name already registered in this query chain")
	ErrAliasInvalidName      = errors.New("gplus: invalid alias name (must match [a-zA-Z_][a-zA-Z0-9_]{0,31})")
	ErrFieldAddrUnregistered = errors.New("gplus: field address not registered to any model or alias in this query chain")
	ErrAliasNotInChain       = errors.New("gplus: alias instance does not belong to this query chain")
	ErrSubqueryOuterNil      = errors.New("gplus: SubQuery outer is nil")
	ErrAliasQueryNil         = errors.New("gplus: As query is nil")
	ErrAliasRevoked          = errors.New("gplus: alias instance has been revoked by Clear()")
)

新增哨兵错误(7 个)

View Source
var (
	ErrQueryNil          = errors.New("gplus: query cannot be nil")
	ErrRawSQLEmpty       = errors.New("gplus: raw sql cannot be empty")
	ErrDeleteEmpty       = errors.New("gplus: delete content is empty")
	ErrUpdateEmpty       = errors.New("gplus: update content is empty")
	ErrUpdateNoCondition = errors.New("gplus: update requires at least one condition to prevent full-table update")
	ErrTransactionReq    = errors.New("gplus: locking query must be executed within a transaction")
	ErrDefaultsNil       = errors.New("gplus: defaults cannot be nil, use &T{} to create a zero-value record explicitly")
	ErrRestoreEmpty      = errors.New("gplus: restore condition is empty")
	ErrOnConflictInvalid = errors.New("gplus: OnConflict config invalid: DoNothing is mutually exclusive with DoUpdates/DoUpdateAll/UpdateExprs; DoUpdateAll is mutually exclusive with DoUpdates/UpdateExprs")
	ErrOptimisticLock    = errors.New("gplus: optimistic lock conflict (version mismatch or row not found)")
	ErrSubqueryNil       = errors.New("gplus: subquery is nil")
)
View Source
var (
	ErrColumnNotFound = errors.New("gplus: column name not found for pointer")
	ErrInvalidPointer = errors.New("gplus: argument must be a struct field pointer")
	ErrColumnEmpty    = errors.New("gplus: column name cannot be empty")
)
View Source
var DataRuleKey = dataRuleKey{}

DataRuleKey 是用于在 context.Context 中存储 []DataRule 的键。 使用示例:ctx = context.WithValue(ctx, gplus.DataRuleKey, rules)

View Source
var ErrFindOneAsConflict = errors.New("gplus: FindOneAs 不可与 q.Limit() / q.Page() 组合调用")

ErrFindOneAsConflict 表示 FindOneAs 与 q.Limit() / q.Page() 组合调用。 内部 First 会追加 LIMIT 1,与已有 LIMIT 叠加,部分 DB 行为未定义。

Functions

func As added in v0.8.0

func As[X any](q AnyQuery, alias string) *X

As 在 q(含 outerQueryRef 链)上注册一个 X 类型的 alias 实例。

错误处理:

  • q == nil:panic ErrAliasQueryNil(N5:API 入口编程错误,无 q 可挂错误)
  • name 不符合白名单正则:累积 ErrAliasInvalidName,返回规范单例 fallback
  • name 已在链中存在:累积 ErrAliasDuplicate,返回首次注册实例(决策 1B)

返回的 *X 实例字段地址独立于规范单例,仅用于取字段地址。

func Avg added in v0.3.0

func Avg[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any) (R, error)

Avg 对指定列求平均值,R 为数值类型。

【R 支持类型】int64 / float64 / string 安全;MySQL DECIMAL 列建议 R=float64 (database/sql 通过 strconv 路径正确转换);time.Time / sql.NullX 作为 R 无意义。

【callback chain】走 Query callback chain(v0.7.0 起),下游 isolation/审计 callback 会触发。

【col 字符串警告】传字符串列名时 gplus 不做白名单校验,禁止将用户输入直接传入 col。

func AvgTx added in v0.3.0

func AvgTx[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any, tx *gorm.DB) (R, error)

AvgTx 支持事务的列平均值

func FindAs added in v0.7.0

func FindAs[T any, Dest any, D comparable](
	r *Repository[D, T], q *Query[T], dest *[]Dest,
) error

FindAs 投影查询(多行)。dest 必须是 *[]Element 切片指针。

走 GORM Query callback chain,下游挂在 Query chain 上的隔离/审计 callback 会触发。

【迁移提示】若现有代码用 q.ToDB(db).Model(&T{}).Scan(&rows) / .Rows() / .Row(), 必须改用 gplus.FindAs。前者绕过 Query callback chain,会导致下游隔离/审计 callback 不触发,可能引发跨租户数据泄露 / 审计日志缺失(详见 README "已知陷阱")。

【副作用】调用 FindAs 后 q.conditions 会被永久追加 DataRule 条件 (dataRuleApplied 保护幂等),q 不应再跨不同 ctx 复用。与 List/Sum 等行为一致。

【调用形态】Go 1.18+ 类型推导后无需写类型参数:

var rows []UserVO
err := gplus.FindAs(repo, q, &rows)

func FindAsTx added in v0.7.0

func FindAsTx[T any, Dest any, D comparable](
	r *Repository[D, T], q *Query[T], dest *[]Dest, tx *gorm.DB,
) error

FindAsTx 支持事务的 FindAs。tx 为 nil 时与 FindAs 等价。

func FindOneAs added in v0.7.0

func FindOneAs[T any, Dest any, D comparable](
	r *Repository[D, T], q *Query[T], dest *Dest,
) error

FindOneAs 投影查询(单行)。dest 是 *Element。

无匹配时返回 gorm.ErrRecordNotFound(与 GetById 既有语义一致)。

走 GORM Query callback chain,下游挂在 Query chain 上的隔离/审计 callback 会触发。

【迁移提示】若现有代码用 q.ToDB(db).Model(&T{}).Limit(1).Scan(&one),必须改用 gplus.FindOneAs。前者绕过 Query chain,可能引发跨租户数据泄露 / 审计日志缺失。

【约束】FindOneAs 不可与 q.Limit() / q.Page() 组合 —— 内部 First 会追加 LIMIT 1, 与已有 LIMIT 叠加部分 DB 行为未定义。组合调用会立即返回 ErrFindOneAsConflict。

【实测确认】GORM v1.31.x First(dest) 不会用 dest 的 schema 覆盖已设置的 Model(new(T));下游 isolation callback 拿到的 Schema.Table 仍为 T 表名。 (由 TestGORMCallbackBehaviorProbe 永久守护)

func FindOneAsTx added in v0.7.0

func FindOneAsTx[T any, Dest any, D comparable](
	r *Repository[D, T], q *Query[T], dest *Dest, tx *gorm.DB,
) error

FindOneAsTx 支持事务的 FindOneAs。

func IsNotFound

func IsNotFound(err error) bool

IsNotFound 判断错误是否为「记录不存在」

func Max added in v0.3.0

func Max[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any) (R, error)

Max 对指定列求最大值,R 为数值类型。

【R 支持类型】int64 / float64 / string 安全;MySQL DECIMAL 列建议 R=float64 (database/sql 通过 strconv 路径正确转换);time.Time / sql.NullX 作为 R 无意义。

【callback chain】走 Query callback chain(v0.7.0 起),下游 isolation/审计 callback 会触发。

【col 字符串警告】传字符串列名时 gplus 不做白名单校验,禁止将用户输入直接传入 col。

func MaxTx added in v0.3.0

func MaxTx[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any, tx *gorm.DB) (R, error)

MaxTx 支持事务的列最大值

func Min added in v0.3.0

func Min[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any) (R, error)

Min 对指定列求最小值,R 为数值类型。

【R 支持类型】int64 / float64 / string 安全;MySQL DECIMAL 列建议 R=float64 (database/sql 通过 strconv 路径正确转换);time.Time / sql.NullX 作为 R 无意义。

【callback chain】走 Query callback chain(v0.7.0 起),下游 isolation/审计 callback 会触发。

【col 字符串警告】传字符串列名时 gplus 不做白名单校验,禁止将用户输入直接传入 col。

func MinTx added in v0.3.0

func MinTx[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any, tx *gorm.DB) (R, error)

MinTx 支持事务的列最小值

func Pluck

func Pluck[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any) ([]R, error)

Pluck 提取单列值到泛型切片中,R 为列元素类型。

【R 支持类型】int64 / float64 / string 安全;MySQL DECIMAL 列建议 R=float64 (database/sql 通过 strconv 路径正确转换);time.Time / sql.NullX 作为 R 无意义。

【callback chain】走 Query callback chain(v0.7.0 起),下游 isolation/审计 callback 会触发。

【col 字符串警告】传字符串列名时 gplus 不做白名单校验,禁止将用户输入直接传入 col。

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/glebarez/sqlite"
	"github.com/yi-nanping/gplus"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Article 示例模型
type Article struct {
	ID      uint   `gorm:"primaryKey;autoIncrement"`
	Title   string `gorm:"column:title"`
	Author  string `gorm:"column:author"`
	Views   int    `gorm:"column:views"`
	Deleted gorm.DeletedAt
}

func openExampleDB() *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Discard,
	})
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	if err := db.AutoMigrate(&Article{}); err != nil {
		log.Fatalf("migrate: %v", err)
	}
	return db
}

func main() {
	db := openExampleDB()
	repo := gplus.NewRepository[uint, Article](db)
	ctx := context.Background()

	db.Create(&Article{Title: "A", Author: "Alice", Views: 10})
	db.Create(&Article{Title: "B", Author: "Alice", Views: 20})

	q, m := gplus.NewQuery[Article](ctx)
	q.Eq(&m.Author, "Alice").Order(&m.Views, true)

	titles, err := gplus.Pluck[Article, string, uint](repo, q, &m.Title)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(titles)
}
Output:
[A B]

func PluckTx

func PluckTx[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any, tx *gorm.DB) ([]R, error)

PluckTx 在指定事务中提取单列值到泛型切片中

func RegisterModel

func RegisterModel(models ...any)

RegisterModel 注册模型,解析并缓存字段映射关系。 通常在应用启动时显式调用,也可在首次查询时由框架自动触发。 注意:必须在任何并发查询(NewQuery/NewUpdater)开始前完成调用, 即在 http.ListenAndServe 或 goroutine 启动之前执行,否则可能出现竞态。 实际上,大多数场景无需显式调用本方法——框架会在首次 NewQuery/NewUpdater 时自动注册。 并发安全:多个 goroutine 同时注册同一类型时,只有第一个写入者的 指针会成为规范单例,其余调用无副作用。 传入 nil(无类型 nil)或 typed-nil 指针时会被静默跳过。

func Sum added in v0.3.0

func Sum[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any) (R, error)

Sum 对指定列求和,R 为数值类型。

【R 支持类型】int64 / float64 / string 安全;MySQL DECIMAL 列建议 R=float64 (database/sql 通过 strconv 路径正确转换);time.Time / sql.NullX 作为 R 无意义。

【callback chain】走 Query callback chain(v0.7.0 起),下游 isolation/审计 callback 会触发。

【col 字符串警告】传字符串列名时 gplus 不做白名单校验,禁止将用户输入直接传入 col。

func SumTx added in v0.3.0

func SumTx[T any, R any, D comparable](r *Repository[D, T], q *Query[T], col any, tx *gorm.DB) (R, error)

SumTx 支持事务的列求和

Types

type AnyQuery added in v0.8.0

type AnyQuery interface {
	// contains filtered or unexported methods
}

AnyQuery 是 Query[T] 和 Updater[T] 的 phantom 标签接口 业务代码无法实现(gplusCore 返回 unexported 类型)

type ColumnInfo

type ColumnInfo struct {
	Offset     uintptr
	ColumnName string
}

ColumnInfo 存储字段偏移量和列名的关系

type DataRule

type DataRule struct {
	Column    string   // 规则字段 (例如: "dept_id");仅允许字母/数字/下划线及单个点分隔的表名前缀(如 "table.col"),含括号或运算符的表达式会被拒绝
	Condition string   // 规则条件 (例如: "=", "IN", "LIKE")
	Value     string   // 规则值   (例如: "1001");IN/NOT IN/BETWEEN 建议使用 Values
	Values    []string // IN/NOT IN/BETWEEN 的多值列表,优先于 Value 的逗号分隔解析
}

DataRule 对外开放的核心规则字段

type OnConflict added in v0.4.0

type OnConflict struct {
	// Columns 冲突检测列(唯一索引/主键),支持字段指针或字符串列名。
	Columns []any
	// DoNothing 冲突时跳过,不执行任何更新(INSERT IGNORE / DO NOTHING)。
	// 与 DoUpdates/DoUpdateAll/UpdateExprs 互斥。
	DoNothing bool
	// DoUpdates 冲突时按 EXCLUDED 覆盖指定列,支持字段指针或字符串列名。
	// 与 DoNothing/DoUpdateAll 互斥,可与 UpdateExprs 组合。
	DoUpdates []any
	// DoUpdateAll 冲突时按 EXCLUDED 覆盖除主键外的所有列。
	// 与 DoNothing/DoUpdates/UpdateExprs 互斥。
	DoUpdateAll bool
	// UpdateExprs 冲突时按自定义表达式更新,key 为列名,value 为 gorm.Expr 或普通值。
	// 示例:{"count": gorm.Expr("count + VALUES(count)")}
	// 可与 DoUpdates 组合,不可与 DoNothing/DoUpdateAll 共用。
	UpdateExprs map[string]any
}

OnConflict 定义 INSERT ... ON CONFLICT 的冲突处理策略。 Columns 为空时:MySQL 按表内唯一索引自动判定;Postgres/SQLite 须显式指定冲突列。

type Query

type Query[T any] struct {
	ScopeBuilder
	// contains filtered or unexported fields
}

func NewQuery

func NewQuery[T any](ctx context.Context) (*Query[T], *T)

NewQuery 创建泛型查询构建器,同时返回类型 T 的规范实例指针。 所有字段指针参数(如 &model.Name)必须来自返回的 *T 实例。 ctx 用于传递请求级上下文(DataRule、超时等),可传 context.Background()。

func NewQueryAs added in v0.8.0

func NewQueryAs[T any](ctx context.Context, alias string) (*Query[T], *T)

NewQueryAs 创建 Query 并给主表起 alias。

返回的 *T 是独立 alias 实例(字段地址绑定到 alias),而非规范单例; 使用 &t.Field 时解析为 "alias.col" 而非 "table.col"。

alias 必须满足 ^[a-zA-Z_][a-zA-Z0-9_]{0,31}$,否则累积 ErrAliasInvalidName。

func SubQuery added in v0.8.0

func SubQuery[X any](outer AnyQuery) (*Query[X], *X)

SubQuery 派生子查询。sub.outerQueryRef = outer,sub 主表 alias 自动设为表名(如 Order → "orders")。

错误处理:

  • outer == nil:返回带 ErrSubqueryOuterNil 累积错误的 dud sub(H4:与 errs 哲学一致,不 panic)

sub.ctx 来自 outer.ctx(透传)。sub 不自动应用 outer 的 DataRule(保持 v0.6.0 既有语义)。

func SubQueryAs added in v0.8.0

func SubQueryAs[X any](outer AnyQuery, alias string) (*Query[X], *X)

SubQueryAs 派生子查询并指定主表 alias。 等价于 SubQuery + As(sub, alias),但合并为单步以避免双初始化。

错误处理:

  • outer == nil:返回带 ErrSubqueryOuterNil 累积错误的 dud sub(H4)
  • alias 不合法:由 NewQueryAs 内部 As 校验逻辑累积 ErrAliasInvalidName

func (*Query[T]) And

func (q *Query[T]) And(fn func(sub *Query[T])) *Query[T]

And 开启一个带括号的 AND 嵌套块

func (*Query[T]) Between

func (q *Query[T]) Between(col any, val1 any, val2 any) *Query[T]

Between 区间查询

func (*Query[T]) BuildQuery added in v0.8.0

func (q *Query[T]) BuildQuery() func(db *gorm.DB) *gorm.DB

BuildQuery 覆盖 ScopeBuilder.BuildQuery(promoted method),在闭包入口添加 v0.8.0 决策 1B errs 短路。

行为:

  • 若 q.core.errs 非空(含 As 重名/Clear 后用残骸等错误):返回的 closure 调用时 直接 db.AddError 返回,不生成 SQL
  • 若 q.core.errs 为空:与 ScopeBuilder.BuildQuery 既有行为一致(DataRule + 条件构建)

这确保所有 .Scopes(q.BuildQuery()) 生产路径(repo.GetById/List/Page/FindAs 等) 在 As 重名 / Clear 残骸 / 字段地址未注册等错误下自动短路,不依赖调用方先调 GetError。

func (*Query[T]) BuildQueryDB added in v0.8.0

func (q *Query[T]) BuildQueryDB(db *gorm.DB) *gorm.DB

BuildQueryDB 将当前 Query 的条件应用到 db 并返回带条件的 *gorm.DB。 v0.8.0 决策 1B:若 q.core.errs 非空(含 As 重名等错误),直接返回带聚合错误的 db,不生成 SQL。 防止重名 alias / Clear 后用 alias 等错误被快乐路径 SQL 静默掩盖。

与 BuildQuery()(返回闭包供 Scopes 使用)互补; BuildQueryDB 用于需要直接获得 *gorm.DB 的场景(如 DataRuleBuilder 链式调用末尾)。

func (*Query[T]) Clear added in v0.2.1

func (q *Query[T]) Clear()

Clear 重写 Query 的清除逻辑

func (*Query[T]) Context

func (q *Query[T]) Context() context.Context

Context 获取上下文

func (*Query[T]) CrossJoin deprecated

func (q *Query[T]) CrossJoin(table string) *Query[T]

CrossJoin 交叉连接:返回笛卡尔积 注意:交叉连接通常不需要 ON 条件

Deprecated: use CrossJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) CrossJoinAs added in v0.8.0

func (q *Query[T]) CrossJoinAs(alias any) *Query[T]

CrossJoinAs 类型安全的 CROSS JOIN(无 ON 条件)

func (*Query[T]) DataRuleBuilder

func (q *Query[T]) DataRuleBuilder() *Query[T]

DataRuleBuilder 从上下文中提取规则并应用到查询中。 对同一个 Query 对象只执行一次,防止多次调用(如 Page 内的 Count+Find)重复追加条件。

func (*Query[T]) Distinct

func (q *Query[T]) Distinct(cols ...any) *Query[T]

Distinct 去重 支持传入字段指针或字符串,例如:q.Distinct(&user.Name, &user.Age) 如果不传参数,则默认为 DISTINCT *

func (*Query[T]) Eq

func (q *Query[T]) Eq(col any, val any) *Query[T]

Eq 等于

func (*Query[T]) EqSub added in v0.6.0

func (q *Query[T]) EqSub(col any, sub Subquerier) *Query[T]

EqSub 子查询:col = (subquery)。详见 InSub 关于 sub 生命周期约束。

func (*Query[T]) Exists added in v0.8.0

func (q *Query[T]) Exists(sub Subquerier) *Query[T]

Exists 添加 EXISTS 子查询条件(AND)。

sub 通常通过 SubQuery[X](q) 派生,能引用外层 q 的字段(相关子查询)。 sub == nil 时累积 ErrSubqueryNil;sub.GetError() 非空时在 BuildQuery 执行时透传到外层 GORM 错误链。

func (*Query[T]) FullJoin deprecated

func (q *Query[T]) FullJoin(table string, on string, args ...any) *Query[T]

FullJoin 全外连接:返回左右表中所有的记录

Deprecated: use FullJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) FullJoinAs added in v0.8.0

func (q *Query[T]) FullJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Query[T]

FullJoinAs 类型安全的 FULL JOIN

func (*Query[T]) Ge

func (q *Query[T]) Ge(col any, val any) *Query[T]

Ge 大于等于

func (*Query[T]) GetError

func (q *Query[T]) GetError() error

GetError 将所有累积的错误合并为一个返回(含 alias core 中的错误)

func (*Query[T]) Group

func (q *Query[T]) Group(cols ...any) *Query[T]

Group 分组

func (*Query[T]) Gt

func (q *Query[T]) Gt(col any, val any) *Query[T]

Gt 大于

func (*Query[T]) GtSub added in v0.6.0

func (q *Query[T]) GtSub(col any, sub Subquerier) *Query[T]

GtSub > 子查询:col > (subquery)。详见 InSub。

func (*Query[T]) GteSub added in v0.6.0

func (q *Query[T]) GteSub(col any, sub Subquerier) *Query[T]

GteSub >= 子查询:col >= (subquery)。详见 InSub。

func (*Query[T]) Having

func (q *Query[T]) Having(col string, op string, val any) *Query[T]

Having 添加分组过滤条件 示例: q.Having("COUNT(id)", OpGt, 10)

func (*Query[T]) HavingGroup

func (q *Query[T]) HavingGroup(fn func(sub *Query[T])) *Query[T]

HavingGroup 嵌套 Having

func (*Query[T]) In

func (q *Query[T]) In(col any, val any) *Query[T]

In 包含

func (*Query[T]) InSub added in v0.6.0

func (q *Query[T]) InSub(col any, sub Subquerier) *Query[T]

InSub IN 子查询:col IN (subquery)。

sub 必须为类型安全 *Query[X];外部冒名实现被 gplusSubquery() guard 阻止。 sub 应在传入前完成构建(包括 Select/Where/DataRuleBuilder),传入后再修改会 反映到最终 SQL(延迟调用语义)。

sub 中需用 Select(&col) 限定单列;否则 GORM 运行时报多列错误。

func (*Query[T]) InnerJoin deprecated

func (q *Query[T]) InnerJoin(table string, on string, args ...any) *Query[T]

InnerJoin 内连接:仅返回两个表中匹配的记录(交集)

Deprecated: use InnerJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) InnerJoinAs added in v0.8.0

func (q *Query[T]) InnerJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Query[T]

InnerJoinAs 类型安全的 INNER JOIN

func (*Query[T]) IsEmpty

func (q *Query[T]) IsEmpty() bool

IsEmpty 判断是否为空查询(无任何类型安全条件)。 注意:仅检查通过 Eq/In/Between 等类型安全 API 添加的条件; 通过 WithScope 注入的自定义 scope 函数不计入此判断。

func (*Query[T]) IsNotNull

func (q *Query[T]) IsNotNull(col any) *Query[T]

IsNotNull 不为空

func (*Query[T]) IsNull

func (q *Query[T]) IsNull(col any) *Query[T]

IsNull 为空

func (*Query[T]) IsUnscoped

func (q *Query[T]) IsUnscoped() bool

IsUnscoped 是否为不带软删除的查询

func (*Query[T]) Le

func (q *Query[T]) Le(col any, val any) *Query[T]

Le 小于等于

func (*Query[T]) LeftJoin deprecated

func (q *Query[T]) LeftJoin(table string, on string, args ...any) *Query[T]

LeftJoin 左连接:返回左表所有记录,即使右表无匹配

Deprecated: use LeftJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) LeftJoinAs added in v0.8.0

func (q *Query[T]) LeftJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Query[T]

LeftJoinAs 类型安全的 LEFT JOIN(v0.8.0 alias 体系)。

alias 必须由 As[X](q, ...) 创建的实例,且属于当前 q 链; leftCol / rightCol 为任意一侧的字段指针(主表或副表均可); extraSQL 为额外的 ON 条件 SQL 片段,仅含 ? 占位符(如 "AND o.status = ?")—— 绝不直接拼接用户输入,extraSQL 本身不经 fmt.Sprintf 处理; extraArgs 对应占位符参数,走 GORM 参数化预编译,与 db.Joins(sql, args...) 同语义。

错误处理:alias 不属于 q 链时累积 ErrAliasNotInChain 并跳过该 JOIN(保持链式)。

func (*Query[T]) Like

func (q *Query[T]) Like(col any, val string) *Query[T]

Like 模糊查询

func (*Query[T]) LikeLeft

func (q *Query[T]) LikeLeft(col any, val string) *Query[T]

LikeLeft 左模糊查询

func (*Query[T]) LikeRight

func (q *Query[T]) LikeRight(col any, val string) *Query[T]

LikeRight 右模糊查询

func (*Query[T]) Limit

func (q *Query[T]) Limit(limit int) *Query[T]

Limit 分页

func (*Query[T]) LockRead

func (q *Query[T]) LockRead() *Query[T]

LockRead 加共享锁 (SELECT ... FOR SHARE) 允许其他事务读取,但阻止其他事务修改

func (*Query[T]) LockWithOpt

func (q *Query[T]) LockWithOpt(strength, options string) *Query[T]

LockWithOpt 高级加锁 (支持 NOWAIT 或 SKIP LOCKED) strength: "UPDATE" / "SHARE" options: "NOWAIT" / "SKIP LOCKED"

func (*Query[T]) LockWrite

func (q *Query[T]) LockWrite() *Query[T]

LockWrite 加排他锁 (SELECT ... FOR UPDATE) 阻止其他事务读取或修改,直到本事务结束

func (*Query[T]) Lt

func (q *Query[T]) Lt(col any, val any) *Query[T]

Lt 小于

func (*Query[T]) LtSub added in v0.6.0

func (q *Query[T]) LtSub(col any, sub Subquerier) *Query[T]

LtSub < 子查询:col < (subquery)。详见 InSub。

func (*Query[T]) LteSub added in v0.6.0

func (q *Query[T]) LteSub(col any, sub Subquerier) *Query[T]

LteSub <= 子查询:col <= (subquery)。详见 InSub。

func (*Query[T]) NaturalJoin deprecated

func (q *Query[T]) NaturalJoin(table string) *Query[T]

NaturalJoin 自然连接:基于相同列名自动匹配

Deprecated: use NaturalJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) NaturalJoinAs added in v0.8.0

func (q *Query[T]) NaturalJoinAs(alias any) *Query[T]

NaturalJoinAs 类型安全的 NATURAL JOIN(无 ON 条件)

func (*Query[T]) Ne

func (q *Query[T]) Ne(col any, val any) *Query[T]

Ne 不等于

func (*Query[T]) NeSub added in v0.6.0

func (q *Query[T]) NeSub(col any, sub Subquerier) *Query[T]

NeSub <> 子查询:col <> (subquery)。详见 InSub。

func (*Query[T]) NotBetween

func (q *Query[T]) NotBetween(col any, val1 any, val2 any) *Query[T]

NotBetween 区间查询(不包含边界)

func (*Query[T]) NotExists added in v0.8.0

func (q *Query[T]) NotExists(sub Subquerier) *Query[T]

NotExists 添加 NOT EXISTS 子查询条件(AND)。详见 Exists。

func (*Query[T]) NotIn

func (q *Query[T]) NotIn(col any, val any) *Query[T]

NotIn 不包含

func (*Query[T]) NotInSub added in v0.6.0

func (q *Query[T]) NotInSub(col any, sub Subquerier) *Query[T]

NotInSub NOT IN 子查询。详见 InSub。

func (*Query[T]) NotLike

func (q *Query[T]) NotLike(col any, val string) *Query[T]

NotLike 不包含

func (*Query[T]) Offset

func (q *Query[T]) Offset(offset int) *Query[T]

Offset 偏移

func (*Query[T]) Omit

func (q *Query[T]) Omit(cols ...any) *Query[T]

Omit 排除某些字段(不查询某些字段)

func (*Query[T]) Or

func (q *Query[T]) Or(fn func(sub *Query[T])) *Query[T]

Or 开启一个带括号的 OR 嵌套块

func (*Query[T]) OrBetween

func (q *Query[T]) OrBetween(col any, val1 any, val2 any) *Query[T]

OrBetween 区间查询(或)

func (*Query[T]) OrEq

func (q *Query[T]) OrEq(col any, val any) *Query[T]

OrEq 等于(或)

func (*Query[T]) OrEqSub added in v0.6.0

func (q *Query[T]) OrEqSub(col any, sub Subquerier) *Query[T]

OrEqSub 子查询(或)。详见 EqSub。

func (*Query[T]) OrExists added in v0.8.0

func (q *Query[T]) OrExists(sub Subquerier) *Query[T]

OrExists 添加 OR EXISTS 子查询条件。与 Exists 相同,但使用 OR 逻辑。

func (*Query[T]) OrGe

func (q *Query[T]) OrGe(col any, val any) *Query[T]

OrGe 大于等于(或)

func (*Query[T]) OrGt

func (q *Query[T]) OrGt(col any, val any) *Query[T]

OrGt 大于(或)

func (*Query[T]) OrGtSub added in v0.6.0

func (q *Query[T]) OrGtSub(col any, sub Subquerier) *Query[T]

OrGtSub > 子查询(或)。详见 GtSub。

func (*Query[T]) OrGteSub added in v0.6.0

func (q *Query[T]) OrGteSub(col any, sub Subquerier) *Query[T]

OrGteSub >= 子查询(或)。详见 GteSub。

func (*Query[T]) OrHaving

func (q *Query[T]) OrHaving(col string, op string, val any) *Query[T]

OrHaving 添加 OR 分组过滤

func (*Query[T]) OrIn

func (q *Query[T]) OrIn(col any, val any) *Query[T]

OrIn 包含(或)

func (*Query[T]) OrInSub added in v0.6.0

func (q *Query[T]) OrInSub(col any, sub Subquerier) *Query[T]

OrInSub IN 子查询(或)。详见 InSub。

func (*Query[T]) OrIsNotNull

func (q *Query[T]) OrIsNotNull(col any) *Query[T]

OrIsNotNull 不为空(或)

func (*Query[T]) OrIsNull

func (q *Query[T]) OrIsNull(col any) *Query[T]

OrIsNull 为空(或)

func (*Query[T]) OrLe

func (q *Query[T]) OrLe(col any, val any) *Query[T]

OrLe 小于等于(或)

func (*Query[T]) OrLike

func (q *Query[T]) OrLike(col any, val string) *Query[T]

OrLike 模糊查询(或)

func (*Query[T]) OrLikeLeft

func (q *Query[T]) OrLikeLeft(col any, val string) *Query[T]

OrLikeLeft 左模糊查询(或)

func (*Query[T]) OrLikeRight

func (q *Query[T]) OrLikeRight(col any, val string) *Query[T]

OrLikeRight 右模糊查询(或)

func (*Query[T]) OrLt

func (q *Query[T]) OrLt(col any, val any) *Query[T]

OrLt 小于(或)

func (*Query[T]) OrLtSub added in v0.6.0

func (q *Query[T]) OrLtSub(col any, sub Subquerier) *Query[T]

OrLtSub < 子查询(或)。详见 LtSub。

func (*Query[T]) OrLteSub added in v0.6.0

func (q *Query[T]) OrLteSub(col any, sub Subquerier) *Query[T]

OrLteSub <= 子查询(或)。详见 LteSub。

func (*Query[T]) OrNe

func (q *Query[T]) OrNe(col any, val any) *Query[T]

OrNe 不等于(或)

func (*Query[T]) OrNeSub added in v0.6.0

func (q *Query[T]) OrNeSub(col any, sub Subquerier) *Query[T]

OrNeSub <> 子查询(或)。详见 NeSub。

func (*Query[T]) OrNotBetween

func (q *Query[T]) OrNotBetween(col any, val1 any, val2 any) *Query[T]

OrNotBetween 区间查询(不包含边界)(或)

func (*Query[T]) OrNotExists added in v0.8.0

func (q *Query[T]) OrNotExists(sub Subquerier) *Query[T]

OrNotExists 添加 OR NOT EXISTS 子查询条件。与 NotExists 相同,但使用 OR 逻辑。

func (*Query[T]) OrNotIn

func (q *Query[T]) OrNotIn(col any, val any) *Query[T]

OrNotIn 不包含(或)

func (*Query[T]) OrNotInSub added in v0.6.0

func (q *Query[T]) OrNotInSub(col any, sub Subquerier) *Query[T]

OrNotInSub NOT IN 子查询(或)。详见 InSub。

func (*Query[T]) OrNotLike

func (q *Query[T]) OrNotLike(col any, val string) *Query[T]

OrNotLike 不包含(或)

func (*Query[T]) OrWhereRaw added in v0.2.0

func (q *Query[T]) OrWhereRaw(sql string, args ...any) *Query[T]

OrWhereRaw 添加原生 SQL 条件(OR)。 参数安全要求与 WhereRaw 相同。

func (*Query[T]) Order

func (q *Query[T]) Order(col any, isAsc bool) *Query[T]

Order 排序

func (*Query[T]) OrderRaw added in v0.2.0

func (q *Query[T]) OrderRaw(expr string) *Query[T]

OrderRaw 添加原生 ORDER BY 表达式,不经转义直接传入 GORM。 适用于含函数调用、CASE WHEN、NULLS LAST 等复杂排序场景。 调用顺序即为最终 SQL ORDER BY 的顺序,可与 Order 混用。 示例:q.OrderRaw("FIELD(status, 'active', 'pending')") 示例:q.OrderRaw("score DESC NULLS LAST") 注意:expr 参数由调用方负责安全性,不可直接拼接用户输入。

func (*Query[T]) OuterJoin deprecated

func (q *Query[T]) OuterJoin(table string, on string, args ...any) *Query[T]

OuterJoin 注意:裸 "OUTER JOIN" 不是标准 SQL,MySQL/PostgreSQL/SQLite 均不支持, 调用此方法将导致数据库语法错误。如需外连接,请使用 FullJoin ("FULL OUTER JOIN")。

Deprecated: use OuterJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) OuterJoinAs added in v0.8.0

func (q *Query[T]) OuterJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Query[T]

OuterJoinAs 类型安全的 OUTER JOIN(部分方言别名为 FULL OUTER JOIN)

func (*Query[T]) Page

func (q *Query[T]) Page(page, pageSize int) *Query[T]

Page 针对page和pageSize的处理

func (*Query[T]) Preload

func (q *Query[T]) Preload(column string, args ...any) *Query[T]

Preload 预加载关联数据 column: 结构体中的关联字段名(通常是字符串,如 "Orders" 或 "User.Role") args: 可选的过滤条件,例如只预加载状态为已支付的订单

func (*Query[T]) RightJoin deprecated

func (q *Query[T]) RightJoin(table string, on string, args ...any) *Query[T]

RightJoin 右连接:返回右表所有记录,即使左表无匹配

Deprecated: use RightJoinAs for type-safe column references. Will be removed in v1.0. Still useful for joining subquery tables / function-returning tables / USING clauses where alias instances cannot represent the join target.

func (*Query[T]) RightJoinAs added in v0.8.0

func (q *Query[T]) RightJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Query[T]

RightJoinAs 类型安全的 RIGHT JOIN(参数语义同 LeftJoinAs)

func (*Query[T]) Select

func (q *Query[T]) Select(cols ...any) *Query[T]

Select 指定查询字段

func (*Query[T]) SelectRaw added in v0.6.0

func (q *Query[T]) SelectRaw(expr string) *Query[T]

SelectRaw 添加原生 SELECT 字段表达式。 expr 为原生 SQL 表达式,不经列名转义直接传入 GORM。 示例:q.SelectRaw("AVG(age)").SelectRaw("COUNT(*) as cnt") 注意:expr 参数由调用方负责安全性,不可直接拼接用户输入。

func (*Query[T]) Table

func (q *Query[T]) Table(name string) *Query[T]

Table 动态指定表名 场景:分表查询或临时表操作

func (*Query[T]) ToCountSQL added in v0.4.0

func (q *Query[T]) ToCountSQL(db *gorm.DB) (string, error)

ToCountSQL 将当前查询转换为 COUNT SQL 字符串,不执行实际查询。 对应 Page() 内部的 COUNT 路径,可用于确认分页计数时的实际 SQL。

func (*Query[T]) ToDB

func (q *Query[T]) ToDB(db *gorm.DB) *gorm.DB

ToDB 将当前 Query 的条件转换为 GORM 的 DB 对象 注意:这不会执行查询,只会生成带有条件的 DB 实例,常用于子查询 1. 构建子查询 (查部门 ID) subQuery, _ := gplus.NewQuery[Dept](ctx) subQuery.Eq(&Dept.Name, "IT").Select(&Dept.Id) 2. 获取 DB 实例 (通常 Repository 会暴露 GetDB,或者直接从外部传入) 这里的 repo 是 UserRepo db := userRepo.GetDB() 3. 构建主查询 (查用户) mainQuery, _ := gplus.NewQuery[User](ctx) 【关键】:使用 ToDB 将 subQuery 转换为 GORM 对象,放入 In 条件中 mainQuery.In(&User.DeptId, subQuery.ToDB(db)) 4. 执行查询 users, err := userRepo.List(mainQuery)

func (*Query[T]) ToSQL added in v0.4.0

func (q *Query[T]) ToSQL(db *gorm.DB) (string, error)

ToSQL 将当前查询转换为 SELECT SQL 字符串,不执行实际查询。 返回的 SQL 已将参数内联,仅用于调试展示,不可直接作为参数化查询使用。 db 提供方言信息(引号类型),不会发出任何网络请求。

func (*Query[T]) Unscoped

func (q *Query[T]) Unscoped() *Query[T]

Unscoped 物理查询(包含被软删除的数据)

func (*Query[T]) WhereRaw added in v0.2.0

func (q *Query[T]) WhereRaw(sql string, args ...any) *Query[T]

WhereRaw 添加原生 SQL 条件(AND)。 sql 为完整条件片段,args 为参数绑定值,防止 SQL 注入。 示例:q.WhereRaw("YEAR(created_at) = ?", 2024) 注意:sql 参数由调用方负责安全性,不可直接拼接用户输入。

func (*Query[T]) WithScope added in v0.3.0

func (q *Query[T]) WithScope(fn func(*gorm.DB) *gorm.DB) *Query[T]

WithScope 注入自定义 GORM scope 函数,作为封装层的逃生口。 适用于封装层无法覆盖的边缘查询场景,多次调用按顺序叠加执行。

注意事项:

  • fn 不可为 nil
  • 不要在 fn 内调用 Limit/Offset/Unscoped,会覆盖外层设置
  • fn 应保持无状态、可重入,避免引入隐式副作用
  • 优先使用类型安全的 API(Eq/In/WhereRaw 等),WithScope 作为最后手段

type Repository

type Repository[D comparable, T any] struct {
	// contains filtered or unexported fields
}

Repository 泛型仓储,提供标准 CRUD D: ID类型 (int, string, etc.), T: 实体类型

func NewRepository

func NewRepository[D comparable, T any](db *gorm.DB) *Repository[D, T]

NewRepository 构造函数

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/glebarez/sqlite"
	"github.com/yi-nanping/gplus"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Article 示例模型
type Article struct {
	ID      uint   `gorm:"primaryKey;autoIncrement"`
	Title   string `gorm:"column:title"`
	Author  string `gorm:"column:author"`
	Views   int    `gorm:"column:views"`
	Deleted gorm.DeletedAt
}

func openExampleDB() *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Discard,
	})
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	if err := db.AutoMigrate(&Article{}); err != nil {
		log.Fatalf("migrate: %v", err)
	}
	return db
}

func main() {
	db := openExampleDB()
	repo := gplus.NewRepository[uint, Article](db)

	ctx := context.Background()

	// 插入记录
	a := &Article{Title: "Hello gplus", Author: "Alice", Views: 100}
	if err := repo.Save(ctx, a); err != nil {
		log.Fatal(err)
	}

	// 按主键查询
	got, err := repo.GetById(ctx, a.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got.Title)
}
Output:
Hello gplus

func (*Repository[D, T]) Chunk added in v0.3.0

func (r *Repository[D, T]) Chunk(q *Query[T], batchSize int, fn func([]T) error) error

Chunk 分批处理查询结果,每批调用 fn 一次。fn 返回非 nil 错误时立即终止并返回该错误。 batchSize 建议在 100-1000 之间,过小会增加 DB 往返次数,过大会占用大量内存。

内部基于 GORM FindInBatches,使用主键游标分页(WHERE id > lastID),性能优于 OFFSET 分页。 主键类型说明:

  • 自增 int:最优,单调递增,索引连续扫描。
  • UUID v7 / 时间有序字符串:接近 int,近似有序,性能良好。
  • UUID v4 / 随机字符串:功能正确,但随机分布导致索引跳跃,性能弱于 int。
  • 复合主键:GORM 仅用第一个主键字段做游标,可能漏行,不建议使用 Chunk,请改用 Page()。

func (*Repository[D, T]) ChunkTx added in v0.3.0

func (r *Repository[D, T]) ChunkTx(q *Query[T], batchSize int, tx *gorm.DB, fn func([]T) error) error

ChunkTx 支持事务的分批处理。tx=nil 时降级为普通连接,行为等同 Chunk。

func (*Repository[D, T]) Count

func (r *Repository[D, T]) Count(q *Query[T]) (int64, error)

Count 统计数量

func (*Repository[D, T]) CountTx

func (r *Repository[D, T]) CountTx(q *Query[T], tx *gorm.DB) (int64, error)

CountTx 支持事务的统计查询

func (*Repository[D, T]) CreateBatch

func (r *Repository[D, T]) CreateBatch(ctx context.Context, entities []*T, batchSize int) error

CreateBatch 批量插入(分批执行,适合大批量数据)。 batchSize 控制每批插入的记录数,防止超出数据库单次 SQL 参数限制。

func (*Repository[D, T]) CreateBatchTx

func (r *Repository[D, T]) CreateBatchTx(ctx context.Context, entities []*T, batchSize int, tx *gorm.DB) error

CreateBatchTx 事务批量插入(分批执行,适合大批量数据)。

func (*Repository[D, T]) DecrBy added in v0.3.0

func (r *Repository[D, T]) DecrBy(u *Updater[T], col any, delta int64) (int64, error)

DecrBy 原子自减指定列。等价于 IncrBy(u, col, -delta)。

func (*Repository[D, T]) DecrByTx added in v0.3.0

func (r *Repository[D, T]) DecrByTx(u *Updater[T], col any, delta int64, tx *gorm.DB) (int64, error)

DecrByTx 支持事务的原子自减。

func (*Repository[D, T]) DeleteByCond

func (r *Repository[D, T]) DeleteByCond(q *Query[T]) (int64, error)

DeleteByCond 根据条件删除

func (*Repository[D, T]) DeleteByCondTx added in v0.2.0

func (r *Repository[D, T]) DeleteByCondTx(q *Query[T], tx *gorm.DB) (int64, error)

DeleteByCondTx 事务根据条件删除

func (*Repository[D, T]) DeleteById

func (r *Repository[D, T]) DeleteById(ctx context.Context, id D) (int64, error)

DeleteById 根据 ID 删除

func (*Repository[D, T]) DeleteByIdTx

func (r *Repository[D, T]) DeleteByIdTx(ctx context.Context, id D, tx *gorm.DB) (int64, error)

DeleteByIdTx 事务删除。 若 ctx 中携带 DataRule,仅当记录满足权限条件时才删除;跨租户 ID 返回 affected=0。

func (*Repository[D, T]) DeleteByIds added in v0.3.0

func (r *Repository[D, T]) DeleteByIds(ctx context.Context, ids []D) (int64, error)

DeleteByIds 批量按主键删除,ids 为空时直接返回 0,不发 SQL

func (*Repository[D, T]) DeleteByIdsTx added in v0.3.0

func (r *Repository[D, T]) DeleteByIdsTx(ctx context.Context, ids []D, tx *gorm.DB) (int64, error)

DeleteByIdsTx 支持事务的批量主键删除,ids 为空时直接返回 0,不发 SQL。 若 ctx 中携带 DataRule,仅删除满足权限条件的记录;跨租户 ID 被静默跳过。

func (*Repository[D, T]) Exists added in v0.3.0

func (r *Repository[D, T]) Exists(q *Query[T]) (bool, error)

Exists 检查是否存在满足条件的记录。 若 q 不含任何条件,等价于检查表中是否存在任何记录(全表 LIMIT 1)。

func (*Repository[D, T]) ExistsTx added in v0.3.0

func (r *Repository[D, T]) ExistsTx(q *Query[T], tx *gorm.DB) (bool, error)

ExistsTx 支持事务的存在性检查

func (*Repository[D, T]) FirstOrCreate added in v0.3.0

func (r *Repository[D, T]) FirstOrCreate(q *Query[T], defaults *T) (data T, created bool, err error)

FirstOrCreate 按条件查找记录,不存在时用 defaults 创建。 返回值:(record, created, error),created=true 表示本次新建。 defaults 不可为 nil,否则返回 ErrDefaultsNil。 内部使用事务保证查询与创建的原子性。

func (*Repository[D, T]) FirstOrUpdate added in v0.3.0

func (r *Repository[D, T]) FirstOrUpdate(q *Query[T], u *Updater[T], defaults *T) (data T, created bool, err error)

FirstOrUpdate 按条件查找记录,找到则执行 Updater 更新,未找到则用 defaults 创建。 返回值:(record, created, error),created=true 表示本次新建。 defaults 不可为 nil,否则返回 ErrDefaultsNil。 内部使用事务保证查找与更新/创建的原子性。

func (*Repository[D, T]) GetById

func (r *Repository[D, T]) GetById(ctx context.Context, id D) (T, error)

GetById 根据主键查询

func (*Repository[D, T]) GetByIdTx

func (r *Repository[D, T]) GetByIdTx(ctx context.Context, id D, tx *gorm.DB) (data T, err error)

GetByIdTx 支持事务的查询。 若 ctx 中携带 DataRule,将在 WHERE 中追加对应条件;跨租户 ID 返回 gorm.ErrRecordNotFound。

func (*Repository[D, T]) GetByIds added in v0.3.0

func (r *Repository[D, T]) GetByIds(ctx context.Context, ids []D) ([]T, error)

GetByIds 批量按主键查询,ids 为空时直接返回空切片

func (*Repository[D, T]) GetByIdsTx added in v0.3.0

func (r *Repository[D, T]) GetByIdsTx(ctx context.Context, ids []D, tx *gorm.DB) ([]T, error)

GetByIdsTx 支持事务的批量主键查询,ids 为空时直接返回空切片。 若 ctx 中携带 DataRule,结果集仅包含满足权限条件的记录;跨租户 ID 被静默过滤。

func (*Repository[D, T]) GetByLock

func (r *Repository[D, T]) GetByLock(q *Query[T], tx *gorm.DB) (*T, error)

GetByLock 专门的带锁查询方法 强制要求传入 tx,因为不在事务里的锁是没有意义的

func (*Repository[D, T]) GetDB

func (r *Repository[D, T]) GetDB() *gorm.DB

GetDB 获取当前 Repository 绑定的 DB 实例 注意:请勿通过此方法修改 db.Config 或关闭连接

func (*Repository[D, T]) GetOne

func (r *Repository[D, T]) GetOne(q *Query[T]) (data T, err error)

GetOne 根据条件查询单条

func (*Repository[D, T]) GetOneTx

func (r *Repository[D, T]) GetOneTx(q *Query[T], tx *gorm.DB) (data T, err error)

GetOneTx 支持事务的单条查询

func (*Repository[D, T]) IncrBy added in v0.3.0

func (r *Repository[D, T]) IncrBy(u *Updater[T], col any, delta int64) (int64, error)

IncrBy 原子自增指定列。col 须为 NewUpdater 返回的 *T 实例的字段指针。 u 用于指定 WHERE 条件;未设置条件时返回 ErrUpdateNoCondition 以防全表更新。

func (*Repository[D, T]) IncrByTx added in v0.3.0

func (r *Repository[D, T]) IncrByTx(u *Updater[T], col any, delta int64, tx *gorm.DB) (int64, error)

IncrByTx 支持事务的原子自增。

func (*Repository[D, T]) InsertBatchOnConflict added in v0.4.0

func (r *Repository[D, T]) InsertBatchOnConflict(ctx context.Context, entities []T, oc OnConflict) error

InsertBatchOnConflict 执行带冲突处理的批量插入。entities 为空时直接返回,不发 SQL。

func (*Repository[D, T]) InsertBatchOnConflictTx added in v0.4.0

func (r *Repository[D, T]) InsertBatchOnConflictTx(ctx context.Context, entities []T, oc OnConflict, tx *gorm.DB) error

InsertBatchOnConflictTx 支持事务的批量冲突插入。

func (*Repository[D, T]) InsertOnConflict added in v0.4.0

func (r *Repository[D, T]) InsertOnConflict(ctx context.Context, entity *T, oc OnConflict) error

InsertOnConflict 执行带冲突处理的单条插入。 根据 OnConflict 策略决定:冲突时跳过(DoNothing)、覆盖指定列(DoUpdates)、覆盖所有列(DoUpdateAll)、原子表达式更新(UpdateExprs)。

func (*Repository[D, T]) InsertOnConflictTx added in v0.4.0

func (r *Repository[D, T]) InsertOnConflictTx(ctx context.Context, entity *T, oc OnConflict, tx *gorm.DB) error

InsertOnConflictTx 支持事务的单条冲突插入。

func (*Repository[D, T]) Last added in v0.3.0

func (r *Repository[D, T]) Last(q *Query[T]) (data T, err error)

Last 按主键倒序取第一条记录,语义与 GetOne 对称(GetOne 用 First)。

func (*Repository[D, T]) LastTx added in v0.3.0

func (r *Repository[D, T]) LastTx(q *Query[T], tx *gorm.DB) (data T, err error)

LastTx 支持事务的 Last。

func (*Repository[D, T]) List

func (r *Repository[D, T]) List(q *Query[T]) (data []T, err error)

List 根据条件查询列表

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/glebarez/sqlite"
	"github.com/yi-nanping/gplus"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Article 示例模型
type Article struct {
	ID      uint   `gorm:"primaryKey;autoIncrement"`
	Title   string `gorm:"column:title"`
	Author  string `gorm:"column:author"`
	Views   int    `gorm:"column:views"`
	Deleted gorm.DeletedAt
}

func openExampleDB() *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Discard,
	})
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	if err := db.AutoMigrate(&Article{}); err != nil {
		log.Fatalf("migrate: %v", err)
	}
	return db
}

func main() {
	db := openExampleDB()
	repo := gplus.NewRepository[uint, Article](db)
	ctx := context.Background()

	db.Create(&Article{Title: "Go generics", Author: "Alice", Views: 200})
	db.Create(&Article{Title: "GORM tips", Author: "Bob", Views: 50})
	db.Create(&Article{Title: "gplus guide", Author: "Alice", Views: 300})

	q, m := gplus.NewQuery[Article](ctx)
	q.Eq(&m.Author, "Alice").Ge(&m.Views, 100).Order(&m.Views, false)

	articles, err := repo.List(q)
	if err != nil {
		log.Fatal(err)
	}
	for _, a := range articles {
		fmt.Printf("%s (%d)\n", a.Title, a.Views)
	}
}
Output:
gplus guide (300)
Go generics (200)

func (*Repository[D, T]) ListMap added in v0.3.0

func (r *Repository[D, T]) ListMap(q *Query[T], keyFn func(T) D) (map[D]T, error)

ListMap 查询列表并按 keyFn 转换为 map。重复 key 时后者覆盖前者。

func (*Repository[D, T]) ListMapTx added in v0.3.0

func (r *Repository[D, T]) ListMapTx(q *Query[T], keyFn func(T) D, tx *gorm.DB) (map[D]T, error)

ListMapTx 支持事务的 ListMap。

func (*Repository[D, T]) ListTx

func (r *Repository[D, T]) ListTx(q *Query[T], tx *gorm.DB) (data []T, err error)

ListTx 支持事务的列表查询

func (*Repository[D, T]) NewQuery added in v0.3.1

func (r *Repository[D, T]) NewQuery(ctx context.Context) (*Query[T], *T)

NewQuery 创建与当前 Repository 同类型的查询构建器,无需重复指定泛型参数。 等价于 gplus.NewQuery[T](ctx),但类型由 Repository 自动推导。

func (*Repository[D, T]) NewQueryAs added in v0.8.0

func (r *Repository[D, T]) NewQueryAs(ctx context.Context, alias string) (*Query[T], *T)

NewQueryAs 创建带主表 alias 的 Query,便捷方法。 等价于 gplus.NewQueryAs[T](ctx, alias),但类型由 Repository 自动推导。

func (*Repository[D, T]) NewUpdater added in v0.3.1

func (r *Repository[D, T]) NewUpdater(ctx context.Context) (*Updater[T], *T)

NewUpdater 创建与当前 Repository 同类型的更新构建器,无需重复指定泛型参数。 等价于 gplus.NewUpdater[T](ctx),但类型由 Repository 自动推导。

func (*Repository[D, T]) Page

func (r *Repository[D, T]) Page(q *Query[T], skipCount bool) (data []T, total int64, err error)

Page 分页查询。 skipCount=true 时跳过 COUNT 查询,total 恒为 0,适合已知总数或不需要总数的场景(性能更优)。 skipCount=false 时先执行 COUNT,若总数为 0 则直接返回,不再执行 Find。

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/glebarez/sqlite"
	"github.com/yi-nanping/gplus"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Article 示例模型
type Article struct {
	ID      uint   `gorm:"primaryKey;autoIncrement"`
	Title   string `gorm:"column:title"`
	Author  string `gorm:"column:author"`
	Views   int    `gorm:"column:views"`
	Deleted gorm.DeletedAt
}

func openExampleDB() *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Discard,
	})
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	if err := db.AutoMigrate(&Article{}); err != nil {
		log.Fatalf("migrate: %v", err)
	}
	return db
}

func main() {
	db := openExampleDB()
	repo := gplus.NewRepository[uint, Article](db)
	ctx := context.Background()

	for i := 1; i <= 5; i++ {
		db.Create(&Article{Title: fmt.Sprintf("Article %d", i), Author: "Alice", Views: i * 10})
	}

	q, _ := gplus.NewQuery[Article](ctx)
	q.Limit(3).Offset(0)

	list, total, err := repo.Page(q, false)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("total=%d page=%d\n", total, len(list))
}
Output:
total=5 page=3

func (*Repository[D, T]) PageTx

func (r *Repository[D, T]) PageTx(q *Query[T], skipCount bool, tx *gorm.DB) (data []T, total int64, err error)

PageTx 支持事务的分页查询。skipCount 语义同 Page。

func (*Repository[D, T]) RawExec

func (r *Repository[D, T]) RawExec(ctx context.Context, sql string, args ...any) (int64, error)

RawExec 执行原生 SQL(如 INSERT, UPDATE, DELETE 或 DDL 语句) 返回受影响的行数

func (*Repository[D, T]) RawExecTx

func (r *Repository[D, T]) RawExecTx(ctx context.Context, tx *gorm.DB, sql string, args ...any) (int64, error)

RawExecTx 在事务中执行原生 SQL(如 INSERT, UPDATE, DELETE 或 DDL 语句) 返回受影响的行数

func (*Repository[D, T]) RawQuery

func (r *Repository[D, T]) RawQuery(ctx context.Context, sql string, args ...any) ([]T, error)

RawQuery 执行原生查询 SQL,并将结果映射到当前 Repository 的实体切片中 适用场景:复杂的 JOIN 查询或存储过程

func (*Repository[D, T]) RawQueryTx

func (r *Repository[D, T]) RawQueryTx(ctx context.Context, tx *gorm.DB, sql string, args ...any) ([]T, error)

RawQueryTx 在事务中执行原生查询 SQL,并将结果映射到当前 Repository 的实体切片中

func (*Repository[D, T]) RawScan

func (r *Repository[D, T]) RawScan(ctx context.Context, dest any, sql string, args ...any) error

RawScan 执行原生 SQL 并将结果映射到【任意】指定的结构体或变量中 适用场景:聚合查询(如 SUM/COUNT)或统计类报表

func (*Repository[D, T]) RawScanTx

func (r *Repository[D, T]) RawScanTx(ctx context.Context, tx *gorm.DB, dest any, sql string, args ...any) error

RawScanTx 在事务中执行原生 SQL 并将结果映射到【任意】指定的结构体或变量中

func (*Repository[D, T]) Restore added in v0.3.0

func (r *Repository[D, T]) Restore(ctx context.Context, id D) (int64, error)

Restore 恢复软删除记录(将 deleted_at 置 NULL)。 返回受影响的行数:1 表示成功恢复,0 表示记录不存在或未被软删除。 注意:模型须包含 gorm.DeletedAt 字段,否则行为未定义。

func (*Repository[D, T]) RestoreByCond added in v0.3.0

func (r *Repository[D, T]) RestoreByCond(q *Query[T]) (int64, error)

RestoreByCond 按条件批量恢复软删除记录(将 deleted_at 置 NULL)。 空条件会返回 ErrRestoreEmpty,防止意外全表恢复。 返回受影响的行数。

func (*Repository[D, T]) RestoreByCondTx added in v0.3.0

func (r *Repository[D, T]) RestoreByCondTx(q *Query[T], tx *gorm.DB) (int64, error)

RestoreByCondTx 支持事务的按条件批量恢复软删除。

func (*Repository[D, T]) RestoreTx added in v0.3.0

func (r *Repository[D, T]) RestoreTx(ctx context.Context, id D, tx *gorm.DB) (int64, error)

RestoreTx 支持事务的软删除恢复。 注意:启用 DataRule 时,跨租户记录返回 affected==0(不会恢复)。

func (*Repository[D, T]) Save

func (r *Repository[D, T]) Save(ctx context.Context, entity *T) error

Save 纯 INSERT(非 upsert)。 警告:无论 entity 是否携带主键,均执行 INSERT,不会更新已有记录。 若需 insert-or-update 语义,请使用 Upsert。

func (*Repository[D, T]) SaveBatch

func (r *Repository[D, T]) SaveBatch(ctx context.Context, entities []T) error

SaveBatch 批量纯 INSERT(一次性,适合小批量数据)。 警告:底层调用 GORM Create,执行纯插入而非 upsert。 大批量数据请使用 CreateBatch 以控制每批插入数量。

func (*Repository[D, T]) SaveBatchTx

func (r *Repository[D, T]) SaveBatchTx(ctx context.Context, entities []T, tx *gorm.DB) error

SaveBatchTx 事务批量纯 INSERT(一次性,适合小批量数据)。

func (*Repository[D, T]) SaveTx

func (r *Repository[D, T]) SaveTx(ctx context.Context, entity *T, tx *gorm.DB) error

SaveTx 事务纯 INSERT(非 upsert)。

func (*Repository[D, T]) ToCountSQL added in v0.4.0

func (r *Repository[D, T]) ToCountSQL(q *Query[T]) (string, error)

ToCountSQL 将查询转换为 COUNT SQL 字符串,使用 Repository 内部的 DB,不执行实际查询。

func (*Repository[D, T]) ToSQL added in v0.4.0

func (r *Repository[D, T]) ToSQL(q *Query[T]) (string, error)

ToSQL 将查询转换为 SELECT SQL 字符串,使用 Repository 内部的 DB,不执行实际查询。

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/glebarez/sqlite"
	"github.com/yi-nanping/gplus"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Article 示例模型
type Article struct {
	ID      uint   `gorm:"primaryKey;autoIncrement"`
	Title   string `gorm:"column:title"`
	Author  string `gorm:"column:author"`
	Views   int    `gorm:"column:views"`
	Deleted gorm.DeletedAt
}

func openExampleDB() *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Discard,
	})
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	if err := db.AutoMigrate(&Article{}); err != nil {
		log.Fatalf("migrate: %v", err)
	}
	return db
}

func main() {
	db := openExampleDB()
	repo := gplus.NewRepository[uint, Article](db)
	ctx := context.Background()

	q, m := gplus.NewQuery[Article](ctx)
	q.Eq(&m.Author, "Alice").Gt(&m.Views, 100)

	sql, err := repo.ToSQL(q)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(sql != "")
}
Output:
true

func (*Repository[D, T]) ToUpdateSQL added in v0.4.0

func (r *Repository[D, T]) ToUpdateSQL(u *Updater[T]) (string, error)

ToUpdateSQL 将更新操作转换为 UPDATE SQL 字符串,使用 Repository 内部的 DB,不执行实际查询。

func (*Repository[D, T]) Transaction

func (r *Repository[D, T]) Transaction(ctx context.Context, fn func(tx *gorm.DB) error) error

Transaction 封装 GORM 的事务闭包模式 fn: 事务内的业务逻辑,如果返回 error,事务会自动回滚

func (*Repository[D, T]) UpdateByCond

func (r *Repository[D, T]) UpdateByCond(u *Updater[T]) (int64, error)

UpdateByCond 执行条件更新(不带事务)

func (*Repository[D, T]) UpdateByCondTx added in v0.2.0

func (r *Repository[D, T]) UpdateByCondTx(u *Updater[T], tx *gorm.DB) (int64, error)

UpdateByCondTx 执行条件更新(支持事务)

func (*Repository[D, T]) UpdateById

func (r *Repository[D, T]) UpdateById(ctx context.Context, entity *T) error

UpdateById 根据 ID 更新

func (*Repository[D, T]) UpdateByIdTx

func (r *Repository[D, T]) UpdateByIdTx(ctx context.Context, entity *T, tx *gorm.DB) error

UpdateByIdTx 事务更新。若模型含 `gplus:"version"` 字段则启用乐观锁: WHERE id=? AND version=oldVer,SET ..., version=version+1; affected==0 时返回 ErrOptimisticLock(版本冲突或记录不存在)。 成功后 entity.Version 自动递增,可直接再次调用。

注意:启用 DataRule 时,记录存在但跨租户会返回 affected==0(返回 ErrOptimisticLock), 此时不应无条件重试(重试无法绕过权限)。乐观锁版本冲突与 DataRule 拦截当前共用同一错误码, 调用方需通过其他途径区分(如先 GetById 检查记录是否在权限范围内)。

func (*Repository[D, T]) UpdateByIds added in v0.3.0

func (r *Repository[D, T]) UpdateByIds(ctx context.Context, ids []D, u *Updater[T]) (int64, error)

UpdateByIds 批量按主键更新,ids 为空时直接返回 0,不发 SQL

func (*Repository[D, T]) UpdateByIdsTx added in v0.3.0

func (r *Repository[D, T]) UpdateByIdsTx(ctx context.Context, ids []D, u *Updater[T], tx *gorm.DB) (int64, error)

UpdateByIdsTx 支持事务的批量主键更新。

注意:启用 DataRule 时,记录存在但跨租户会返回 affected==0,此时不应无条件重试 (重试无法绕过权限)。调用方需通过其他途径区分权限拦截与实际无匹配行。

func (*Repository[D, T]) Upsert added in v0.2.0

func (r *Repository[D, T]) Upsert(ctx context.Context, entity *T) error

Upsert 保存或更新单条记录(insert-or-update)。 底层调用 GORM db.Save():有主键时执行 UPDATE 全字段,无主键时执行 INSERT。 注意:UPDATE 会覆盖所有字段(包括零值),如需只更新部分字段请使用 UpdateById/UpdateByCond。

func (*Repository[D, T]) UpsertBatch added in v0.2.0

func (r *Repository[D, T]) UpsertBatch(ctx context.Context, entities []T) error

UpsertBatch 批量保存或更新(insert-or-update,一次性执行)。 底层调用 GORM db.Save(),每条记录按主键决定 INSERT 或 UPDATE。

func (*Repository[D, T]) UpsertBatchTx added in v0.2.0

func (r *Repository[D, T]) UpsertBatchTx(ctx context.Context, entities []T, tx *gorm.DB) error

UpsertBatchTx 事务批量保存或更新(insert-or-update)。

func (*Repository[D, T]) UpsertTx added in v0.2.0

func (r *Repository[D, T]) UpsertTx(ctx context.Context, entity *T, tx *gorm.DB) error

UpsertTx 事务保存或更新单条记录(insert-or-update)。

func (*Repository[D, T]) WithTx

func (r *Repository[D, T]) WithTx(tx *gorm.DB) *Repository[D, T]

WithTx 返回一个新的 Repository 实例,该实例绑定了传入的事务对象 这是一个轻量级的浅拷贝,性能消耗极小

type ScopeBuilder

type ScopeBuilder struct {
	// contains filtered or unexported fields
}

ScopeBuilder 负责将条件转换为 GORM Scope 这是 QueryCond 和 UpdateCond 的基类

func (*ScopeBuilder) BuildCount

func (b *ScopeBuilder) BuildCount() func(*gorm.DB) *gorm.DB

BuildCount 计数构建

func (*ScopeBuilder) BuildDelete

func (b *ScopeBuilder) BuildDelete() func(*gorm.DB) *gorm.DB

BuildDelete 专门用于删除 (Delete) 删除操作最核心的是 Where 和 Unscoped

func (*ScopeBuilder) BuildQuery

func (b *ScopeBuilder) BuildQuery() func(*gorm.DB) *gorm.DB

BuildQuery 专门用于查询 (Find/First/List)

func (*ScopeBuilder) BuildUpdate

func (b *ScopeBuilder) BuildUpdate() func(*gorm.DB) *gorm.DB

BuildUpdate 专门用于更新 (Updates/Update/Save) 更新逻辑不应包含 Distinct, Limit, Offset, Order

func (*ScopeBuilder) Clear

func (b *ScopeBuilder) Clear()

Clear 清空所有查询/构建条件

type Subquerier added in v0.6.0

type Subquerier interface {
	// ToDB 返回可作为 GORM 子查询绑定参数的 *gorm.DB 对象
	ToDB(db *gorm.DB) *gorm.DB

	// GetError 返回构建过程累积的错误
	GetError() error
	// contains filtered or unexported methods
}

Subquerier 子查询契约。任意 *Query[X] 自动满足(X 可与外层 T 不同)。 gplusSubquery() 私有方法限制接口只能由本包实现。

type Updater

type Updater[T any] struct {
	// ScopeBuilder 是 Updater 的核心,用于构建 SQL 语句
	ScopeBuilder
	// contains filtered or unexported fields
}

func NewUpdater

func NewUpdater[T any](ctx context.Context) (*Updater[T], *T)

NewUpdater 创建泛型更新构建器,同时返回类型 T 的规范实例指针。 所有字段指针参数(如 &model.Name)必须来自返回的 *T 实例。 ctx 用于传递请求级上下文,可传 context.Background()。

func (*Updater[T]) And

func (u *Updater[T]) And(fn func(sub *Updater[T])) *Updater[T]

And 开启一个带括号的 AND 嵌套块

示例:u.Eq(&User.Status, 1).And(func(sub *gplus.Updater[User]) {
   sub.Gt(&User.Age, 18).OrEq(&User.IsVip, true)
})

func (*Updater[T]) Between

func (u *Updater[T]) Between(col any, v1, v2 any) *Updater[T]

Between 区间查询

func (*Updater[T]) Clear

func (u *Updater[T]) Clear()

Clear 重写 Updater 的清除逻辑 v0.8.0 N4:翻转所有 alias entry 的 revoked 标志,防 Clear 后用 alias 残骸。 保留 entries(不清空 aliases map),让后续 lookupAddr 命中 revoked 拦截。

func (*Updater[T]) Context

func (u *Updater[T]) Context() context.Context

Context 返回上下文信息,若未设置则返回 context.Background()

func (*Updater[T]) DataRuleBuilder added in v0.2.0

func (u *Updater[T]) DataRuleBuilder() *Updater[T]

DataRuleBuilder 从上下文中提取规则并应用到更新条件中。 对同一个 Updater 对象只执行一次,防止多次调用重复追加条件。

func (*Updater[T]) Eq

func (u *Updater[T]) Eq(col any, val any) *Updater[T]

Eq 等于

func (*Updater[T]) EqSub added in v0.6.0

func (u *Updater[T]) EqSub(col any, sub Subquerier) *Updater[T]

EqSub = 子查询。

func (*Updater[T]) Exists added in v0.8.0

func (u *Updater[T]) Exists(sub Subquerier) *Updater[T]

Exists 添加 EXISTS 子查询条件(AND)。 等价于 WHERE EXISTS (subquery)。 若 sub 为 nil 或子查询有错误,错误会累积到 Updater,可通过 GetError() 获取。

func (*Updater[T]) Ge

func (u *Updater[T]) Ge(col any, val any) *Updater[T]

Ge 大于等于

func (*Updater[T]) GetError

func (u *Updater[T]) GetError() error

GetError 将所有累积的错误合并为一个返回(含 alias core 中的错误)

func (*Updater[T]) Gt

func (u *Updater[T]) Gt(col any, val any) *Updater[T]

Gt 大于

func (*Updater[T]) GtSub added in v0.6.0

func (u *Updater[T]) GtSub(col any, sub Subquerier) *Updater[T]

GtSub > 子查询。

func (*Updater[T]) GteSub added in v0.6.0

func (u *Updater[T]) GteSub(col any, sub Subquerier) *Updater[T]

GteSub >= 子查询。

func (*Updater[T]) In

func (u *Updater[T]) In(col any, val any) *Updater[T]

In 包含

func (*Updater[T]) InSub added in v0.6.0

func (u *Updater[T]) InSub(col any, sub Subquerier) *Updater[T]

InSub IN 子查询:col IN (subquery)。 sub 须为本包创建的 *Query[X](满足 Subquerier 接口)。 参见 Query.InSub。

func (*Updater[T]) InnerJoinAs added in v0.8.0

func (u *Updater[T]) InnerJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Updater[T]

InnerJoinAs Updater 类型安全的 INNER JOIN(v0.8.0 alias 体系,M2 精简)。

func (*Updater[T]) IsEmpty

func (u *Updater[T]) IsEmpty() bool

IsEmpty 是否为空更新(没有设置任何更新字段)

func (*Updater[T]) IsNotNull

func (u *Updater[T]) IsNotNull(col any) *Updater[T]

IsNotNull 不为空

func (*Updater[T]) IsNull

func (u *Updater[T]) IsNull(col any) *Updater[T]

IsNull 为空

func (*Updater[T]) Le

func (u *Updater[T]) Le(col any, val any) *Updater[T]

Le 小于等于

func (*Updater[T]) LeftJoinAs added in v0.8.0

func (u *Updater[T]) LeftJoinAs(alias any, leftCol any, rightCol any, extraSQL string, extraArgs ...any) *Updater[T]

LeftJoinAs Updater 类型安全的 LEFT JOIN(v0.8.0 alias 体系,M2 精简)。

alias 必须由 As[X](u, ...) 创建的实例,且属于当前 u 链; leftCol / rightCol 为任意一侧的字段指针; extraSQL 为额外的 ON 条件 SQL 片段(含 ? 占位符),extraArgs 对应参数。

func (*Updater[T]) Like

func (u *Updater[T]) Like(col any, val string) *Updater[T]

Like 模糊查询

func (*Updater[T]) LikeLeft

func (u *Updater[T]) LikeLeft(col any, val string) *Updater[T]

LikeLeft 左模糊查询

func (*Updater[T]) LikeRight

func (u *Updater[T]) LikeRight(col any, val string) *Updater[T]

LikeRight 右模糊查询

func (*Updater[T]) Lt

func (u *Updater[T]) Lt(col any, val any) *Updater[T]

Lt 小于

func (*Updater[T]) LtSub added in v0.6.0

func (u *Updater[T]) LtSub(col any, sub Subquerier) *Updater[T]

LtSub < 子查询。

func (*Updater[T]) LteSub added in v0.6.0

func (u *Updater[T]) LteSub(col any, sub Subquerier) *Updater[T]

LteSub <= 子查询。

func (*Updater[T]) Ne

func (u *Updater[T]) Ne(col any, val any) *Updater[T]

Ne 不等于

func (*Updater[T]) NeSub added in v0.6.0

func (u *Updater[T]) NeSub(col any, sub Subquerier) *Updater[T]

NeSub <> 子查询。

func (*Updater[T]) NotBetween

func (u *Updater[T]) NotBetween(col any, v1, v2 any) *Updater[T]

NotBetween 区间查询

func (*Updater[T]) NotExists added in v0.8.0

func (u *Updater[T]) NotExists(sub Subquerier) *Updater[T]

NotExists 添加 NOT EXISTS 子查询条件(AND)。详见 Exists。

func (*Updater[T]) NotIn

func (u *Updater[T]) NotIn(col any, val any) *Updater[T]

NotIn 不包含

func (*Updater[T]) NotInSub added in v0.6.0

func (u *Updater[T]) NotInSub(col any, sub Subquerier) *Updater[T]

NotInSub NOT IN 子查询。

注意 MySQL ERROR 1093:UPDATE 同表 IN 子查询限制(如

UPDATE users WHERE id IN (SELECT id FROM users WHERE x)

报错)。可改写为 JOIN UPDATE 或子查询包一层临时表。

func (*Updater[T]) NotLike

func (u *Updater[T]) NotLike(col any, val string) *Updater[T]

NotLike 不模糊查询

func (*Updater[T]) Omit

func (u *Updater[T]) Omit(cols ...any) *Updater[T]

Omit 指定排除哪些字段不更新

func (*Updater[T]) Or

func (u *Updater[T]) Or(fn func(sub *Updater[T])) *Updater[T]

Or 开启一个带括号的 OR 嵌套块

func (*Updater[T]) OrBetween

func (u *Updater[T]) OrBetween(col any, v1, v2 any) *Updater[T]

OrBetween 区间查询

func (*Updater[T]) OrEq

func (u *Updater[T]) OrEq(col any, val any) *Updater[T]

OrEq 等于

func (*Updater[T]) OrEqSub added in v0.6.0

func (u *Updater[T]) OrEqSub(col any, sub Subquerier) *Updater[T]

OrEqSub = 子查询(或)。

func (*Updater[T]) OrExists added in v0.8.0

func (u *Updater[T]) OrExists(sub Subquerier) *Updater[T]

OrExists 添加 OR EXISTS 子查询条件。与 Exists 相同,但使用 OR 逻辑。

func (*Updater[T]) OrGe

func (u *Updater[T]) OrGe(col any, val any) *Updater[T]

OrGe 大于等于

func (*Updater[T]) OrGt

func (u *Updater[T]) OrGt(col any, val any) *Updater[T]

OrGt 大于

func (*Updater[T]) OrGtSub added in v0.6.0

func (u *Updater[T]) OrGtSub(col any, sub Subquerier) *Updater[T]

OrGtSub > 子查询(或)。

func (*Updater[T]) OrGteSub added in v0.6.0

func (u *Updater[T]) OrGteSub(col any, sub Subquerier) *Updater[T]

OrGteSub >= 子查询(或)。

func (*Updater[T]) OrIn

func (u *Updater[T]) OrIn(col any, val any) *Updater[T]

OrIn 包含

func (*Updater[T]) OrInSub added in v0.6.0

func (u *Updater[T]) OrInSub(col any, sub Subquerier) *Updater[T]

OrInSub IN 子查询(或)。

func (*Updater[T]) OrIsNotNull

func (u *Updater[T]) OrIsNotNull(col any) *Updater[T]

OrIsNotNull 不为空

func (*Updater[T]) OrIsNull

func (u *Updater[T]) OrIsNull(col any) *Updater[T]

OrIsNull 为空

func (*Updater[T]) OrLe

func (u *Updater[T]) OrLe(col any, val any) *Updater[T]

OrLe 小于等于

func (*Updater[T]) OrLike

func (u *Updater[T]) OrLike(col any, val string) *Updater[T]

OrLike 模糊查询

func (*Updater[T]) OrLikeLeft

func (u *Updater[T]) OrLikeLeft(col any, val string) *Updater[T]

OrLikeLeft 左模糊查询(或)

func (*Updater[T]) OrLikeRight

func (u *Updater[T]) OrLikeRight(col any, val string) *Updater[T]

OrLikeRight 右模糊查询(或)

func (*Updater[T]) OrLt

func (u *Updater[T]) OrLt(col any, val any) *Updater[T]

OrLt 小于

func (*Updater[T]) OrLtSub added in v0.6.0

func (u *Updater[T]) OrLtSub(col any, sub Subquerier) *Updater[T]

OrLtSub < 子查询(或)。

func (*Updater[T]) OrLteSub added in v0.6.0

func (u *Updater[T]) OrLteSub(col any, sub Subquerier) *Updater[T]

OrLteSub <= 子查询(或)。

func (*Updater[T]) OrNe

func (u *Updater[T]) OrNe(col any, val any) *Updater[T]

OrNe 不等于

func (*Updater[T]) OrNeSub added in v0.6.0

func (u *Updater[T]) OrNeSub(col any, sub Subquerier) *Updater[T]

OrNeSub <> 子查询(或)。

func (*Updater[T]) OrNotBetween

func (u *Updater[T]) OrNotBetween(col any, v1, v2 any) *Updater[T]

OrNotBetween 区间查询

func (*Updater[T]) OrNotExists added in v0.8.0

func (u *Updater[T]) OrNotExists(sub Subquerier) *Updater[T]

OrNotExists 添加 OR NOT EXISTS 子查询条件。与 NotExists 相同,但使用 OR 逻辑。

func (*Updater[T]) OrNotIn

func (u *Updater[T]) OrNotIn(col any, val any) *Updater[T]

OrNotIn 不包含

func (*Updater[T]) OrNotInSub added in v0.6.0

func (u *Updater[T]) OrNotInSub(col any, sub Subquerier) *Updater[T]

OrNotInSub NOT IN 子查询(或)。

func (*Updater[T]) OrNotLike

func (u *Updater[T]) OrNotLike(col any, val string) *Updater[T]

OrNotLike 不模糊查询

func (*Updater[T]) OrWhereRaw added in v0.2.0

func (u *Updater[T]) OrWhereRaw(sql string, args ...any) *Updater[T]

OrWhereRaw 添加原生 SQL 条件(OR)。 参数安全要求与 WhereRaw 相同。

func (*Updater[T]) Select

func (u *Updater[T]) Select(cols ...any) *Updater[T]

Select 指定只更新哪些字段 (即使 setMap 里有其他字段也不会更新)

func (*Updater[T]) Set

func (u *Updater[T]) Set(col any, val any) *Updater[T]

Set 设置更新值 示例: u.Set(&User.Name, "NewName")

func (*Updater[T]) SetExpr

func (u *Updater[T]) SetExpr(col any, expr string, args ...any) *Updater[T]

SetExpr 设置 SQL 表达式更新 (原子更新) 示例: u.SetExpr(&User.Age, "age + ?", 1) -> UPDATE ... SET age = age + 1

func (*Updater[T]) SetMap

func (u *Updater[T]) SetMap(m map[string]any) *Updater[T]

SetMap 批量设置更新内容 注意:map 的 key 必须是数据库列名(snake_case,如 "user_name"), 而非结构体字段名(如 "UserName")。如需类型安全的列名解析,请改用 Set()。

func (*Updater[T]) Table

func (u *Updater[T]) Table(name string) *Updater[T]

Table 动态切换更新表名

func (*Updater[T]) ToSQL added in v0.4.0

func (u *Updater[T]) ToSQL(db *gorm.DB) (string, error)

ToSQL 将当前更新操作转换为 UPDATE SQL 字符串,不执行实际更新。 返回的 SQL 已将参数内联,仅用于调试展示。

func (*Updater[T]) Unscoped

func (u *Updater[T]) Unscoped() *Updater[T]

Unscoped 物理更新,允许更新已被软删除的数据

func (*Updater[T]) UpdateMap

func (u *Updater[T]) UpdateMap() map[string]any

UpdateMap 获取最终的更新 Map 的只读副本 返回副本而非原始引用,防止调用方绕过 Set/SetMap 的列名校验直接修改内部状态

func (*Updater[T]) WhereRaw added in v0.2.0

func (u *Updater[T]) WhereRaw(sql string, args ...any) *Updater[T]

WhereRaw 添加原生 SQL 条件(AND)。 sql 为完整条件片段,args 为参数绑定值,防止 SQL 注入。 示例:u.WhereRaw("YEAR(created_at) = ?", 2024) 注意:sql 参数由调用方负责安全性,不可直接拼接用户输入。

func (*Updater[T]) WithScope added in v0.3.0

func (u *Updater[T]) WithScope(fn func(*gorm.DB) *gorm.DB) *Updater[T]

WithScope 注入自定义 GORM scope 函数,作为封装层的逃生口。 适用于封装层无法覆盖的边缘更新场景,多次调用按顺序叠加执行。

注意事项:

  • fn 不可为 nil
  • 不要在 fn 内调用 Unscoped,会覆盖外层软删除设置
  • fn 应保持无状态、可重入,避免引入隐式副作用
  • 优先使用类型安全的 API(Set/Eq 等),WithScope 作为最后手段

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL