0. 先给结论(超短版)
Odoo ORM 的 SQL 生成主链路是:
search()/search_count()
→ models._search()
→ Domain.optimize_full()
→ Domain._to_sql()(递归)
→ Field.condition_to_sql()(叶子条件编译)
→ Query.select()/subselect()
→ env.execute_query(...)
它不是把 Python 语句逐行翻译,而是把"查询意图(domain AST)"编译成参数化 SQL。
1) 调用栈 A:search_count(domain)
A-1. models.py::search_count()(约 1347)
query = self._search(domain, limit=limit)
return len(query)
search_count不自己写 SQL。- 它拿到
Query对象后调用len(query)。
A-2. query.py::__len__()
- 若 Query 还没 materialize:
- 有
limit/offset时,生成SELECT COUNT(*) FROM (<subquery>) t - 否则生成
SELECT COUNT(*)
- 有
- 然后执行 SQL。
关键点:count 也是复用同一 Query 抽象,不是独立 SQL 通道。
2) 调用栈 B:search(domain, offset, limit, order)
B-1. models.py::search()(约 1363)
return self.search_fetch(domain, [], ...)
search走search_fetch,尽量减少 SQL 次数。
B-2. models.py::search_fetch()
query = self._search(domain, offset=..., limit=..., order=...)
if query.is_empty(): return self.browse()
return self._fetch_query(query, fields_to_fetch)
- 第一步仍是
_search()。 _search只"构建查询",不一定马上执行。
3) 调用栈核心:models.py::_search()(约 5319)
这是最关键函数,逐段看:
C-1. 权限预检
check_access = not (self.env.su or bypass_access)
if check_access:
self.browse().check_access('read')
C-2. 标准化 Domain + active_test 注入
domain = Domain(domain)
if active_test ... and domain 未显式涉及 active 字段:
domain &= Domain(self._active_name, '=', True)
C-3. 关键优化:optimize_full
domain = domain.optimize_full(self)
- 会做 FULL 级优化(可用模型 search method、继承字段解析等)。
- 后续
_to_sql()依赖这个步骤(否则断言失败)。
C-4. 初始化 Query,并注入业务域 SQL
query = Query(self.env, self._table, self._table_sql)
if not domain.is_true():
query.add_where(domain._to_sql(self, self._table, query))
C-5. 注入安全域(record rules)
sec_domain = self.env['ir.rule']._compute_domain(self._name, 'read')
sec_domain = sec_domain.optimize_full(self_sudo)
query.add_where(sec_domain._to_sql(...))
- 这一步非常关键:权限域合并在 SQL WHERE 里。
C-6. ORDER/LIMIT/OFFSET 下推到 Query
if order: query.order = self._order_to_sql(order, query)
if limit is not None: query.limit = limit
if offset is not None: query.offset = offset
返回 Query。
4) Domain 编译栈:domains.py
D-1. Domain.optimize_full(model)(约 435)
- 调
self._optimize(model, OptimizationLevel.FULL)。 _optimize是固定点迭代:一层层优化直到稳定。
D-2. 各种 _to_sql(model, alias, query)
关键节点:
DomainBool._to_sql:TRUE/FALSEDomainNot._to_sql:(child) IS NOT TRUEDomainNary._to_sql(And/Or):拼接AND/ORDomainCondition._to_sql(约 1082):叶子条件编译入口
DomainCondition._to_sql 的核心:
field = self._field(model)
model._check_field_access(field, 'read')
return field.condition_to_sql(field_expr, operator, value, model, alias, query)
这句很关键:domain 叶子最终交给 Field 自己去生成 SQL。
5) Field 编译栈:models._field_to_sql + fields.condition_to_sql
E-1. models.py::_field_to_sql(alias, field_expr, query)(约 2910)
职责:把"字段表达式"变成 SQL 表达式。
- 解析
field_expr(支持 property/path) - 处理 related 非存储字段:路径展开 + join
- 调
field.to_sql(...) - 若有 property:
field.property_to_sql(...)
所以这是"字段表达式 lowering 层"。
E-2. fields.py::condition_to_sql(约 1244)
sql_expr = self._condition_to_sql(...)
if self.company_dependent:
sql_expr = self._condition_to_sql_company(...)
return sql_expr
E-3. fields.py::_condition_to_sql(约 1257)
按 operator 分发:
in/not in(含 null/falsy 语义修正)like/ilike(含 unaccent)>,<,>=,<=any!/not any!(接 Query 或 SQL)
全程使用 SQL("...", params...),参数化而不是直接拼值。
6) 关系字段专线(最容易踩坑)
关系字段并不总走 base Field 逻辑,会被子类覆盖。
F-1. fields_relational.py::Many2one.condition_to_sql(约 467)
当 operator 为 any/not any/any!/not any! 且 field_expr==self.name 时:
- 先取
sql_field = model._field_to_sql(alias, field_expr, query) - 决定走 LEFT JOIN 路线还是子查询 IN/NOT IN 路线
- 若 value 是 Domain:可能转成
comodel._search(value, ...) - 最终组装:
field IN (subselect)或field NOT IN (subselect)- 或 JOIN 后直接把 comodel domain SQL 并入
洞察:many2one 的 any/not any 并不是固定 SQL 模板,会按可空性、正负条件、访问策略动态选策略。
F-2. _RelationalMulti.condition_to_sql(约 766)
给 one2many/many2many 的通用入口:
- 将
in/not in规范成any/not any - 把 value(collection/Domain/SQL/Query)统一转换为 Query
- 调
_condition_to_sql_relational(...)由具体字段落 SQL
F-3. One2many._condition_to_sql_relational(约 1158)
主模板是 EXISTS/NOT EXISTS,核心思路:
- 从 coquery 拿到 inverse 字段
- 生成
EXISTS (SELECT FROM <subquery> WHERE __inverse = alias.id) - 某些非存储 inverse 的场景会退化为先求 ids 再 IN/NOT IN
F-4. Many2many._condition_to_sql_relational(约 1691)
基于关系表(relation/column1/column2)生成:
EXISTS (SELECT 1 FROM rel_table ... AND rel_id2 IN (coquery))- 若 coquery 没有 where,会走更轻量 existence 判断分支。
7) Query 组装栈:query.py
G-1. Query.__init__
- 初始化 FROM 主表
- 维护
_joins、_where_clauses、groupby/having/order/limit/offset
G-2. add_join/add_where
- 渐进式构建 join/where,不立刻执行。
G-3. select(*args)(约 173)
统一输出:
SELECT ...
FROM ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...
OFFSET ...
G-4. subselect(*args)(约 188)
- 生成可嵌套子查询
- 某些情况下移除不必要 ORDER,提高效率
G-5. get_result_ids / __len__ / __iter__
- Query 在"被消费"时才执行 SQL(惰性)
8) ORDER BY 子栈:models._order_to_sql
_order_to_sql(约 5219)会:
- 解析 order 字符串并校验合法性
- 每个字段交给
_order_field_to_sql - many2one 排序时可能递归到 comodel 的
_order_to_sql
这也是为什么看似简单 order='partner_id' 也可能引入 join 与复合排序。
9) read_group 调用栈(聚合)
高层 read_group(约 2749)最终走 _read_group(约 1861):
query = self._search(domain)_read_group_groupby/_read_group_select生成 group/select 项query.order = _read_group_orderby(...)query.having = _read_group_having(...)env.execute_query(query.select(*select_args))
本质 :read_group 是在 _search 结果上继续"加 group/having 的 SQL codegen"。
10) 一条真实"函数级"栈图(search 场景)
Model.search(domain)
-> Model.search_fetch(...)
-> Model._search(domain,...)
-> Domain(domain)
-> Domain.optimize_full(model)
-> Domain._optimize(...)
-> Query(...)
-> Domain._to_sql(model, alias, query)
-> DomainAnd/Or/Not._to_sql(...) [递归]
-> DomainCondition._to_sql(...)
-> field.condition_to_sql(...)
-> field._condition_to_sql(...)
-> model._field_to_sql(...)
-> ir.rule._compute_domain(...)
-> sec_domain._to_sql(...)
-> model._order_to_sql(...)
-> return Query
-> _fetch_query(query,...)
-> query.select(...)
-> env.execute_query(...)
11) 为什么这套架构比"直接拼 SQL"强
- 安全统一:字段读权限、record rules 与业务域在同一 SQL 编译流程。
- 可优化:Domain 可做规则化/简化,不必每个模块自己优化。
- 可组合:Query 可嵌套(subselect),domain 可组合(AND/OR/NOT)。
- 类型语义集中:各字段类型自己决定 SQL 语义,避免散落在业务代码。
12) 你读源码时的最小抓手(建议顺序)
models.py::_search(主干)domains.py::DomainCondition._to_sql(域到字段编译入口)fields.py::_condition_to_sql(操作符细节)fields_relational.py(m2o/o2m/m2m 的 any/not any)query.py::select/subselect(最终 SQL 形态)
按这个顺序读,几乎能覆盖 90% ORM->SQL 的真实路径。