1. 参与者
|-----------------------|-----------------|---------------------------------------|
| 结构 | 文件 | 职责 |
| querySet | orm_queryset.go | 链式状态(cond/limit/orders...),值拷贝不可变 |
| Condition | orm_conds.go | 条件树(params \[\]condValue) |
| dbTables | db_tables.go | JOIN 图:表别名 T0..Tn、ON 生成、SQL 片段 |
| dbBase | db.go | 方言无关主干:Read/ReadBatch/Insert/... |
| db_mysql/postgres/... | db_*.go | dbBaser 方言实现 |
2. Condition:条件树(orm_conds.go)
Go
type Condition struct { params []condValue }
type condValue struct {
exprs []string // ["profile","age"]
args []interface{}
cond *Condition // 嵌套子条件(渲染时加括号)
isOr / isNot / isCond / isRaw bool
sql string // Raw 片段
}
Filter("age__gt",18) → cond.And("age__gt", 18);AndCond(c) 会 clone 子树。And/Or 不 clone 自身,链上重复调用同一 seter 时注意共享(值拷贝的 querySet 会浅拷贝 cond 指针,Filter 才深拷贝)。
3. WHERE 渲染:getCondSQL(db_tables.go:333-401)
Go
遍历 cond.params:
首项后用 "AND "/"OR " 连接;isNot → "NOT "
isCond → 递归 getCondSQL,括号包裹
isRaw → 直接拼 sql
普通项:
exprs 按 "__" split;末段若是 operator(in/between/gt/...完整清单 db.go:38-65)
→ 摘出 operator,前面是字段路径
parseExprs(218-330):沿 Bootstrap 关系图走 exprs
遇 rel/reverse 字段 → joinTables 注册 JOIN(T1、T2...),index 更新
返回 (别名 index, fi)
GenerateOperatorSQL(db.go:1419-1476):
gt → "Tn.col > ?";in → "Tn.col IN (?,?)"
between → "BETWEEN ? AND ?";contains → LIKE '%..%'(转义 \%)、
exact+nil → "IS NULL";isnull → 按 bool
拼出 "Tn.col <op> ?" + args
跨表 Filter 的本质:表达式路径上的每个关系字段都会注册进 JOIN 图,WHERE 与 JOIN 由同一份 dbTables 状态生成,保证一致。
4. JOIN 引擎:dbTables
4.1 结构(db_tables.go:495-501 创建)
newDbTables(mi, d.ins):以主模型为 T0;parseRelated(110-168) 把 RelatedSel 字段按 relDepth 展开;每张表 dbTable{mi, index, skip ...}。
4.2 getJoinSQL(171-215)
对每张 join 表:
Go
ON 条件由字段类型决定:
fk: T0.user_id = T1.id
reverse(one/many): T0.id = T1.user_id
m2m: 经中间表 T2 双 JOIN
INNER JOIN;字段 Null=true 时 LEFT OUTER JOIN
4.3 其余片段
- getGroupSQL(404-425)/getOrderSQL(428-460,order_clause 的 Order 结构:
-前缀降序、__→.)
- getLimitSQL(463-483):limit==0 → DefaultRowsLimit;封顶方言 MaxLimit(mysql 无、sqlite 有)
- getIndexSql(486-492):hints 的 UseIndex 系列转发方言 GenerateSpecifyIndex
5. 主干:readSQL 与 ReadBatch
5.1 ReadBatch(db.go:1095-1290)
Go
容器识别:slice→many;struct→one;未注册模型自动 RegisterModel
确定列集:全部 DBcols + 相关 join 表列
newDbTables → parseRelated
readBatchSQL(1292-1381):
getCondSQL/getGroupSQL/getOrderSQL/getLimitSQL/getJoinSQL/getIndexSql
组装:SELECT [DISTINCT] cols FROM t T0 [idx] [joins] [where] [group] [order] [limit] [FOR UPDATE]
ReplaceMarks:方言占位符(? → $1..,postgres)
QueryContext → 逐行 Scan
setColsValues(1484+):
convertValueFromDB(1506-1667):DB 值→Go 类型的宽松转换
字符串→int/bool/time 按长度 19/10/8 分别解析 datetime/date/time
setFieldValue:指针/Null*/Fielder 各自处理
join 列按别名顺序回填关联对象指针,并回设反向引用(1208-1248)
5.2 Count(1384-1416)
aggregate="COUNT(*)" 复用 readSQL;有 GROUP BY 时外包一层 SELECT COUNT(*) FROM (...) AS T。
5.3 UpdateBatch / DeleteBatch(842-983 / 1014-1092)
- UpdateBatch:支持 JOIN 条件;不支持 join update 的方言(sqlite)生成
UPDATE ... WHERE pk IN (SELECT T0.pk FROM ...)子查询
- DeleteBatch:先 SELECT 命中 PK,再
DELETE WHERE pk IN (...),最后 deleteRels 级联(db.go:987-1011 按 on_delete)
- 列表达式:buildSetSQL(932-983)支持 Col_Add/Minus/Multiply/Except 与位运算
6. 方言层:dbBaser(types.go:624-663)
接口方法(节选):SupportUpdateJoin/OperatorSQL/TableQuote/ReplaceMarks/MaxLimit/HasReturningID/TimeToDB/TimeFromDB/GenerateSpecifyIndex...
|----------|-------------------------|------------------------------------|--------------|-----------------------------------|
| 差异点 | mysql | postgres | sqlite | oracle |
| 占位符 | ? | $n(ReplaceMarks) | ? | :n |
| upsert | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE(≥9.5,必须给冲突列) | ON CONFLICT | MERGE |
| 自增取回 | LastInsertId | LastInsertId | LastInsertId | HasReturningID(INSERT..RETURNING) |
| MaxLimit | 无(可省 LIMIT) | - | LIMIT 必须 | ROWNUM 包装 |
InsertOrUpdate(db.go:528-675)按 alias.Driver 分派------同一段业务语义,四套 SQL。
7. 事务与装饰器
7.1 事务(orm.go:529-602)
Go
Begin → o.db.(txer).BeginTx → txOrm{ormBase{db: &TxDB{tx}}}
DoTx(doTxTemplate):
Begin
panicked := true
defer: panicked || err!=nil → Rollback else Commit
task(ctx, txOrm)
panicked = false
TxDB.RollbackUnlessCommit(239-245):忽略 sql.ErrTxDone 的 Rollback------幂等兜底。
7.2 FilterOrmDecorator(filter_orm_decorator.go)
AddGlobalFilterChain 注册的链在 newDBWithAlias 时生效(只影响之后创建的 Ormer):Invocation{Method/Md/Args/mi/InsideTx/TxName},倒序折叠洋葱;GetTableName/GetPkFieldName 暴露给过滤器做分表路由。内置:bean(事务命名)、otel、opentracing、prometheus。
8. 性能画像
- 反射成本被元数据摊薄;每行 Scan 仍走 interface{} 切片 + convertValueFromDB,超大数据量用 Values/RowsToStruct 减一层 setFieldValue
- stmt 缓存命中时省 Prepare(高并发主收益)
- QuerySeter 值拷贝链:每次 Filter 复制 ~15 字段结构体,廉价;但复用 seter 基座时 Cond 指针共享是隐蔽坑(S12 §2)
- JOIN 逐表展开,深度无硬限------深链查询(DBC 不当)会炸 SQL 长度
9. 设计赏析
- 单一状态源:dbTables 同时喂 WHERE/JOIN/SELECT 列,杜绝"条件与连接不一致"这类 bug。
- 方言收口:主干零 if driver==;新增方言=实现 dbBaser + 注册 dbBasers。
- 模板事务:doTxTemplate 的 panicked 标志是 Go 闭包事务的经典写法,值得抄。
- 宽松类型转换:DB 字符串→Go 数字/时间的 fallback,兼容脏数据;代价是转换失败静默零值------读 convertValueFromDB 注释体会取舍。