Domain → SQL compiler.
A domain is a list of leaves and/or Odoo-style prefix operators &,
|, !. Adjacent leaves without operators are implicitly ANDed
(see normalize_domain). The legacy ("__or__", "=", [sub_leaves…])
leaf is accepted and expanded to | groups before compilation.
Attrs may be dotted (country_id.code, tag_ids.name); paths compile
to memoized LEFT JOINs (pure Many2one chains) or per-leaf EXISTS
subqueries (One2many / Many2many hops).
Public surface
domain_to_sql(domain, model_cls) -> (where, params, joins)
normalize_domain(domain) -> list
iter_domain_leaves(domain) -> iterator of leaf tuples
Operators on leaves: =, !=, <, <=, >, >=, in,
not in, like, ilike. Empty in / not in short-circuit.
Collection paths accept an optional fourth element — {"all": True} —
for universal quantification (NOT EXISTS over members that fail).
is_domain_leaf
is_domain_leaf(token: Any) -> bool
True when token is a (field, op, value[, opts]) leaf.
Source code in pyvelm/domain.py
| def is_domain_leaf(token: Any) -> bool:
"""True when *token* is a ``(field, op, value[, opts])`` leaf."""
if not isinstance(token, (list, tuple)) or len(token) not in (3, 4):
return False
if not isinstance(token[0], str):
return False
return token[0] not in _POLISH_OPS
|
expand_or_groups
expand_or_groups(domain: list) -> list
Expand legacy ("__or__", …) leaves into | prefix groups.
Source code in pyvelm/domain.py
| def expand_or_groups(domain: list) -> list:
"""Expand legacy ``("__or__", …)`` leaves into ``|`` prefix groups."""
out: list = []
for item in domain:
if (
isinstance(item, (list, tuple))
and len(item) == 3
and item[0] == "__or__"
):
subs = list(item[2] or [])
if not subs:
continue
if len(subs) == 1:
out.append(subs[0])
else:
out.extend(["|"] * (len(subs) - 1))
out.extend(subs)
else:
out.append(item)
return out
|
normalize_domain
normalize_domain(domain: list) -> list
Make implicit AND explicit (Odoo normalize_domain semantics).
Source code in pyvelm/domain.py
| def normalize_domain(domain: list) -> list:
"""Make implicit AND explicit (Odoo ``normalize_domain`` semantics)."""
if not domain:
return []
result: list = []
expected = 1
op_arity = {"!": 1, "&": 2, "|": 2}
for token in domain:
if expected == 0:
result.insert(0, "&")
expected = 1
if is_domain_leaf(token):
result.append(tuple(token))
expected -= 1
elif token in op_arity:
result.append(token)
expected += op_arity[token] - 1
else:
raise ValueError(f"Invalid domain token {token!r}")
if expected:
raise ValueError(f"Invalid domain {domain!r}")
return result
|
iter_domain_leaves
iter_domain_leaves(domain: Iterable) -> Iterator[tuple]
Yield every leaf tuple in domain (after normalize / __or__ expand).
Source code in pyvelm/domain.py
| def iter_domain_leaves(domain: Iterable) -> Iterator[tuple]:
"""Yield every leaf tuple in *domain* (after normalize / ``__or__`` expand)."""
if not domain:
return
norm = normalize_domain(expand_or_groups(list(domain)))
tree, end = _parse_polish(norm, 0)
if end != len(norm):
raise ValueError("Trailing tokens in domain")
def _walk(node):
kind = node[0]
if kind == "leaf":
yield node[1]
elif kind == "!":
yield from _walk(node[1])
else:
yield from _walk(node[1])
yield from _walk(node[2])
yield from _walk(tree)
|
domain_to_sql
domain_to_sql(domain: Iterable[tuple[str, str, Any]] | None, model_cls, registry, *, joins: list[str] | None = None, join_aliases: dict[tuple, str] | None = None, join_counter: list[int] | None = None, capabilities=None) -> tuple[str, list[Any], str]
Compile a domain to SQL.
When joins / join_aliases / join_counter are passed, JOIN
emission appends to those shared structures (used by the report compiler
so column and filter paths reuse the same _jN aliases). In that
mode the third return value is always "" — read joins instead.
Source code in pyvelm/domain.py
| def domain_to_sql(
domain: Iterable[tuple[str, str, Any]] | None,
model_cls,
registry,
*,
joins: list[str] | None = None,
join_aliases: dict[tuple, str] | None = None,
join_counter: list[int] | None = None,
capabilities=None,
) -> tuple[str, list[Any], str]:
"""Compile a domain to SQL.
When ``joins`` / ``join_aliases`` / ``join_counter`` are passed, JOIN
emission appends to those shared structures (used by the report compiler
so column and filter paths reuse the same ``_jN`` aliases). In that
mode the third return value is always ``""`` — read ``joins`` instead.
"""
from .domain_sa import domain_to_sql as _compile_domain_to_sql
return _compile_domain_to_sql(
domain,
model_cls,
registry,
capabilities=capabilities,
joins=joins,
join_aliases=join_aliases,
join_counter=join_counter,
)
|