lode/query/expr
The query expression tree and its typed builder combinators.
Ecto’s query macro escapes Elixir AST into an internal expression struct.
We build that tree directly with functions. Static types replace Ecto’s
runtime quoted_type inference:
FieldRef(row, a)is a typed reference to a column of typeaon a source of row typerow(usually produced by generated per-schema accessors). The comparison combinators only accept a matchinga, soeq(user_age, "thirty")is a compile error.- Literal values ride inside
ExprasLit(Value); the SQL renderer extracts them into$nparameters, so callers never manage param indices.
Types
The expression tree consumed by the planner / SQL renderer.
pub type Expr {
Col(binding: Int, name: String)
OuterCol(alias: String, name: String)
Lit(value.Value)
BinOp(op: Op, left: Expr, right: Expr)
Not(Expr)
IsNil(Expr)
InList(Expr, List(Expr))
Call(name: String, args: List(Expr))
As(expr: Expr, label: String)
Fragment(sql: String, args: List(Expr))
Over(
call: Expr,
partition_by: List(Expr),
order_by: List(WindowOrder),
)
OverNamed(call: Expr, window: String)
}
Constructors
-
Col(binding: Int, name: String)A column reference: binding index + column name.
-
OuterCol(alias: String, name: String)A reference to a column of an outer query, by its alias, from inside a correlated subquery (e.g.
EXISTS (... WHERE c.post_id = p.id)references the outerp). The alias is rendered verbatim — the subquery is lexically nested, so the outer binding is already in scope. SQL-only. -
Lit(value.Value)A literal value (rendered as a
$nparameter). -
-
Not(Expr) -
IsNil(Expr) -
expr IN (e1, e2, ...). -
Call(name: String, args: List(Expr))A function/aggregate call: name + arguments, e.g.
count(*),sum(x). -
As(expr: Expr, label: String)A projection alias:
<expr> AS "<label>". Only meaningful inside a SELECT list — used to give join columns non-colliding result keys (e.g.u."id" AS "u__id"). Carries no parameters. -
Fragment(sql: String, args: List(Expr))A raw SQL splice (Ecto’s
fragment): each?insqlis replaced by the matching expression fromargs, in order; literals among the args still become$nparameters. The escape hatch for SQL the expression model doesn’t cover. SQL-only — the in-memory adapter cannot evaluate fragments (they never match there). -
Over( call: Expr, partition_by: List(Expr), order_by: List(WindowOrder), )A window-function application with an inline window (Ecto’s
over/2):<call> OVER (PARTITION BY ... ORDER BY ...).callis a ranking function (row_number/rank/dense_rank) or an aggregate (sum,count, …). Select-list only. SQL-only — the in-memory adapter does not evaluate window functions. Frames (ROWS BETWEEN ...) are not modeled; drop tofragment/repo.query_rawfor those. -
OverNamed(call: Expr, window: String)A window-function application over a window declared with
query.window(Ecto’sover(expr, :w)):<call> OVER <name>. Select-list only; SQL-only.
A typed reference to a column. row is a phantom tag tying the column to its
schema; a is the column’s runtime type.
pub type FieldRef(row, a) {
FieldRef(
binding: Int,
name: String,
lode_type: type_.LodeType(a),
)
}
Constructors
-
FieldRef( binding: Int, name: String, lode_type: type_.LodeType(a), )
pub type Op {
Eq
Neq
Lt
Gt
Lte
Gte
And
Or
Add
Sub
Mul
Div
Like
ILike
}
Constructors
-
Eq -
Neq -
Lt -
Gt -
Lte -
Gte -
And -
Or -
Add -
Sub -
Mul -
Div -
Like -
ILike
Values
pub fn as_(e: Expr, label: String) -> Expr
Alias a projection expression as <expr> AS "<label>". Select-list only.
pub fn col(f: FieldRef(row, a)) -> Expr
The raw column-reference expression for a typed field (e.g. for select).
pub fn col_as(f: FieldRef(row, a), prefix: String) -> Expr
Project a field under a disambiguating prefix, rendering as
<binding>.<name> AS "<prefix>__<name>". Use this in select for joined
queries so same-named columns from different bindings (e.g. users.id and
posts.id) come back under distinct result keys (u__id, p__id) instead
of colliding in the name-keyed row. The matching loader is
schema.load_prefixed. Select-list only — do not use inside WHERE/ON/ORDER.
pub fn count_all() -> Expr
count(*) — rendered specially (no-arg count becomes count(*)).
pub fn dense_rank() -> Expr
dense_rank() — only meaningful as the callee of over/over_named.
pub fn eq_col(
left a: FieldRef(row1, t),
right b: FieldRef(row2, t),
) -> Expr
Compare two columns of the same type (e.g. for join conditions).
pub fn fragment(sql sql: String, args args: List(Expr)) -> Expr
Splice raw SQL into the expression tree (Ecto’s fragment): every ? in
sql is replaced by the matching expression from args, in order.
Literals among the args still render as $n parameters, so values are
never interpolated into the SQL text.
query.where(q, expr.fragment(“lower(?) = ?”, [ expr.col(user_name()), expr.Lit(value.VString(“kip”)), ]))
pub fn in_list(
field f: FieldRef(row, a),
values values: List(a),
) -> Expr
field IN (values) with each value cast through the field’s type.
pub fn json_contains(e: Expr, json json: String) -> Expr
jsonb containment: (<e> @> $n::jsonb), with json (a JSON document as
text) passed as a parameter.
pub fn json_get(e: Expr, key key: String) -> Expr
jsonb member access, staying jsonb: (<e> -> 'key').
pub fn json_get_text(e: Expr, key key: String) -> Expr
jsonb member access as text: (<e> ->> 'key') — the operator-aware way to
compare a json member against a string in WHERE.
pub fn outer(alias alias: String, name name: String) -> Expr
Reference a column of an enclosing query by its alias, from inside a
correlated subquery (Ecto’s parent_as). Pair with query.where_exists:
expr.outer("p", "id") renders as "p"."id".
pub fn over(
call call: Expr,
partition_by partition_by: List(Expr),
order_by order_by: List(WindowOrder),
) -> Expr
Apply a function over an inline window (Ecto’s over/2):
sum(points) OVER (PARTITION BY team ORDER BY points DESC). Use in
select (alias with as_ to name the result column):
expr.as_( expr.over( expr.row_number(), partition_by: [expr.col(team())], order_by: [expr.win_desc(expr.col(points()))], ), “rn”, )
Both engines render identical standard SQL (SQLite since 3.25). SQL-only —
the in-memory adapter does not evaluate windows. Frames (ROWS BETWEEN ...)
are not modeled.
pub fn over_named(call call: Expr, window window: String) -> Expr
Apply a function over a named window declared with query.window (Ecto’s
over(expr, :w)): expr.over_named(expr.rank(), "w") renders
rank() OVER w.
pub fn row_number() -> Expr
row_number() — only meaningful as the callee of over/over_named.
pub fn to_field(ref: FieldRef(row, a)) -> field.Field(row)
Drop a query accessor’s value type to a name-only Field(row), for the
column-list APIs (on_conflict, etc.) that take a homogeneous list of a
schema’s columns — on_conflict.Columns([expr.to_field(user_email())])
reuses the same generated accessor used in queries.
pub fn win_asc(e: Expr) -> WindowOrder
Ascending window order item (<expr> ASC inside OVER (...)).
pub fn win_desc(e: Expr) -> WindowOrder
Descending window order item (<expr> DESC inside OVER (...)).