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:

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 outer p). The alias is rendered verbatim — the subquery is lexically nested, so the outer binding is already in scope. SQL-only.

  • A literal value (rendered as a $n parameter).

  • BinOp(op: Op, left: Expr, right: Expr)
  • Not(Expr)
  • IsNil(Expr)
  • InList(Expr, List(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 ? in sql is replaced by the matching expression from args, in order; literals among the args still become $n parameters. 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 ...). call is 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 to fragment/repo.query_raw for those.

  • OverNamed(call: Expr, window: String)

    A window-function application over a window declared with query.window (Ecto’s over(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

One ORDER BY item inside a window (OVER (... ORDER BY ...)). A window’s ordering is independent of the query’s order_by, so it has its own item type — build with win_asc/win_desc.

pub type WindowOrder {
  WinAsc(Expr)
  WinDesc(Expr)
}

Constructors

Values

pub fn and_(left a: Expr, right b: Expr) -> Expr
pub fn as_(e: Expr, label: String) -> Expr

Alias a projection expression as <expr> AS "<label>". Select-list only.

pub fn avg(f: FieldRef(row, a)) -> Expr
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(f: FieldRef(row, a)) -> Expr
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(field f: FieldRef(row, a), to v: a) -> Expr
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 gt(field f: FieldRef(row, a), to v: a) -> Expr
pub fn gte(field f: FieldRef(row, a), to v: a) -> Expr
pub fn ilike(
  field f: FieldRef(row, String),
  pattern pattern: String,
) -> Expr
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 is_nil(f: FieldRef(row, a)) -> Expr
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 like(
  field f: FieldRef(row, String),
  pattern pattern: String,
) -> Expr
pub fn lt(field f: FieldRef(row, a), to v: a) -> Expr
pub fn lte(field f: FieldRef(row, a), to v: a) -> Expr
pub fn max(f: FieldRef(row, a)) -> Expr
pub fn min(f: FieldRef(row, a)) -> Expr
pub fn neq(field f: FieldRef(row, a), to v: a) -> Expr
pub fn not_(e: Expr) -> Expr
pub fn or_(left a: Expr, right b: Expr) -> Expr
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 rank() -> Expr

rank() — only meaningful as the callee of over/over_named.

pub fn row_number() -> Expr

row_number() — only meaningful as the callee of over/over_named.

pub fn sum(f: FieldRef(row, a)) -> Expr
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 (...)).

Search Document