iceberg · API

iceberg.expressions

Row filters: unbound and bound predicates, binding, projection, evaluators.

An expression tree lives in an arena (Expr.nodes) for the same reason types do — Mojo values are not recursive. Expr.root is the top node.

The pieces, in the order a scan uses them:

  1. parse_filter reads the JSON S-expression DSL (the same one iceberg-rs.mojo's Rust bridge speaks, so oracle plans are directly comparable) into an unbound tree of column names and untyped literals.
  2. bind resolves names to field ids and converts each literal to the column's type, so ["=", "id", 3] on a long column produces a long datum rather than an int one — a mistyped datum silently matches nothing.
  3. rewrite_not pushes negation down to the leaves.
  4. project_inclusive / project_strict push a bound predicate through a partition transform. Inclusive projection is the classic one: it may keep a file that has no matching row, but never drops one that has.
  5. ManifestEvaluator applies the projected predicate to a manifest's partition field summaries; InclusiveMetricsEvaluator applies the original predicate to a data file's per-column bounds and counts.

Structs

ExprNode

struct ExprNode

One node: a boolean connective, a constant, or a predicate on a term.

Implements Copyable, Movable

Fields
op: UInt8
left: Int
right: Int
name: String: Unbound term: the column name.
field_id: Int: Bound term: the field id. -1 while unbound.
prim: UInt8: Bound term: the column's primitive kind.
lits: List[Datum]: Literals; in/not-in may hold many, comparisons exactly one.
raw_lits: List[String]: Unbound literals as raw JSON text, typed at bind time.

Expr

struct Expr

An expression tree in an arena.

Implements Copyable, Movable

Fields
nodes: List[ExprNode]
root: Int

__init__

def __init__(out self)

add

def add(mut self, var n: ExprNode) -> Int

constant

def constant(mut self, v: Bool) -> Int

connective

def connective(mut self, op: UInt8, a: Int, b: Int) -> Int

unbound

def unbound(mut self, op: UInt8, var name: String, var raw: List[String]) -> Int

bound

def bound(mut self, op: UInt8, field_id: Int, prim: UInt8, var lits: List[Datum], var name: String = "") -> Int

name is carried through binding purely so that text() stays round-trippable: a residual is reported as DSL, and DSL names columns.

is_true

def is_true(self, i: Int) -> Bool

is_false

def is_false(self, i: Int) -> Bool

text

def text(self, i: Int) -> String

Round-trippable DSL text — what a scan reports as its residual.

FieldSummary

struct FieldSummary

A manifest list's partitions entry: what one partition field holds across every file in that manifest.

Implements Copyable, Movable

Fields
contains_null: Bool
contains_nan: Bool
has_contains_nan: Bool
lower_bound: List[UInt8]
has_lower: Bool
upper_bound: List[UInt8]
has_upper: Bool

empty

def empty() -> Self

ManifestEvaluator

struct ManifestEvaluator

Applies a partition predicate to a manifest's field summaries.

Constructed from a row filter: the filter is projected inclusively through the manifest's own partition spec first, so the evaluator only ever sees predicates on partition field ids.

Implements Copyable, Movable

Fields
expr: Expr
spec: PartitionSpec
part_type: Schema

__init__

def __init__(out self, row_filter: Expr, spec: PartitionSpec, schema: Schema)

eval

def eval(self, summaries: List[FieldSummary]) -> Bool

True when this manifest might contain a matching row.

ColumnMetrics

struct ColumnMetrics

Per-column statistics carried by a data_file entry.

Implements Copyable, Movable

Fields
field_id: Int
value_count: Int64
has_value_count: Bool
null_value_count: Int64
has_null_value_count: Bool
nan_value_count: Int64
has_nan_value_count: Bool
lower_bound: List[UInt8]
has_lower: Bool
upper_bound: List[UInt8]
has_upper: Bool

blank

def blank(field_id: Int) -> Self

InclusiveMetricsEvaluator

struct InclusiveMetricsEvaluator

Applies a bound row filter to one data file's column metrics.

Implements Copyable, Movable

Fields
expr: Expr
schema: Schema

__init__

def __init__(out self, row_filter: Expr, schema: Schema)

eval

def eval(self, record_count: Int64, metrics: List[ColumnMetrics]) -> Bool

ResidualEvaluator

struct ResidualEvaluator

What is left of a filter once a partition is known to be selected.

If the strict projection says the whole partition matches, the residual is true and no per-row check is needed. Otherwise the original filter stands.

Implements Copyable, Movable

Fields
expr: Expr
strict: Expr
inclusive: Expr
spec: PartitionSpec
conjuncts: List[Int]: The top-level and spine of expr, as node indices.
conjunct_strict: List[Expr]: Each conjunct's own strict projection, one per entry of conjuncts.

__init__

def __init__(out self, row_filter: Expr, spec: PartitionSpec, schema: Schema)

residual_for

def residual_for(self, partition: List[Datum]) -> Expr

The residual filter for one partition tuple.

A conjunct whose own strict projection holds for this partition is satisfied by every row in it, so dropping it leaves the filter's meaning unchanged — and with it goes the column the reader would otherwise have decoded only to check it. On a month-partitioned table, pickup >= '2024-06-01' and pickup < '2024-07-01' and distance > 1 reduces to distance > 1, and the timestamp column is never read: over 3.5M rows that is a 24.7 ms column decode this used to pay per file for an answer the partition already gave. Reducing only when the whole filter is strictly satisfied — which is what this did before — leaves that on the table for every query that mixes a partition-aligned range with a predicate on data.

selects

def selects(self, partition: List[Datum]) -> Bool

Whether the inclusive projection keeps this partition tuple.

Functions

op_name

def op_name(op: UInt8) -> String

negate_op

def negate_op(op: UInt8) -> UInt8

is_predicate

def is_predicate(op: UInt8) -> Bool

parse_filter

def parse_filter(text: String) -> Expr

Parse the filter DSL into an unbound expression.

The grammar is iceberg-rs.mojo's, so the same filter string can be handed to the Rust bridge and to this reader and the plans compared directly.

literal_for

def literal_for(store: TypeStore, type_idx: Int, raw_json: String) -> Datum

Convert one raw JSON literal to the column's type.

Widening conversions the spec allows are done here: an int literal on a long column becomes a long, a string on a date/timestamp column is parsed as ISO-8601, and a number on those columns is taken as the stored integer.

bind

def bind(var e: Expr, schema: Schema, case_sensitive: Bool = True) -> Expr

Resolve names to field ids and type every literal against the schema.

A predicate on a column the schema does not have becomes false (a row can never match a column that is not there) — except is-null, which becomes true, since a missing column reads as all nulls.

rewrite_not

def rewrite_not(e: Expr) -> Expr

Push every not down to the leaves (De Morgan plus op negation).

project_inclusive

def project_inclusive(e: Expr, spec: PartitionSpec, schema: Schema) -> Expr

Project a bound row filter onto the partition tuple, inclusively.

The result is true for every partition that could hold a matching row. It is deliberately loose: dropping a partition that might match would lose data, keeping one that cannot only costs a wasted read.

project_strict

def project_strict(e: Expr, spec: PartitionSpec, schema: Schema) -> Expr

Project strictly: true only for partitions where every row matches.

A scan uses this to drop the filter from a partition's residual — if the whole partition matches, there is nothing left to check per row.

Aliases

comptime OP_TRUE = UInt8(0)
comptime OP_FALSE = UInt8(1)
comptime OP_AND = UInt8(2)
comptime OP_OR = UInt8(3)
comptime OP_NOT = UInt8(4)
comptime OP_IS_NULL = UInt8(5)
comptime OP_NOT_NULL = UInt8(6)
comptime OP_IS_NAN = UInt8(7)
comptime OP_NOT_NAN = UInt8(8)
comptime OP_LT = UInt8(9)
comptime OP_LT_EQ = UInt8(10)
comptime OP_GT = UInt8(11)
comptime OP_GT_EQ = UInt8(12)
comptime OP_EQ = UInt8(13)
comptime OP_NOT_EQ = UInt8(14)
comptime OP_IN = UInt8(15)
comptime OP_NOT_IN = UInt8(16)
comptime OP_STARTS_WITH = UInt8(17)
comptime OP_NOT_STARTS_WITH = UInt8(18)
comptime ROWS_MIGHT_MATCH = True
comptime ROWS_CANNOT_MATCH = False