SQL AST¶
OrionBelt generates SQL through a typed AST rather than string concatenation, which is what makes the output injection-safe by construction.
SQL AST Nodes¶
orionbelt.ast.nodes.Select
dataclass
¶
A complete SELECT statement.
Source code in src/orionbelt/ast/nodes.py
joins = field(default_factory=list)
class-attribute
instance-attribute
¶
Join clauses and unnests, in the order the planner walked the path.
One list rather than two, because the order between them matters: an unnest names its parent, so it has to follow whatever put that parent in scope. Keeping them apart would make the planner interleave them again at render time, from information it no longer has.
grouping = None
class-attribute
instance-attribute
¶
Hierarchical grouping modifier: 'rollup' or 'cube'.
When set, the dialect emits GROUP BY ROLLUP(...) / GROUP BY CUBE(...)
(or ClickHouse-style GROUP BY ... WITH ROLLUP) instead of plain
GROUP BY. The planner is responsible for appending the
GROUPING(dim) AS _g_<dim> columns to the SELECT projection.
orionbelt.ast.nodes.ColumnRef
dataclass
¶
Reference to a column, optionally qualified by table/alias.
abstract_type is the OBML type of the column this names, when the node
was built somewhere that knew it - the two places that resolve a name against
the model, resolution.make_column_expr for the columns: form and
compiler.expr_parser for the expression: one. A ref invented by a
planner or a wrapper (a CTE alias, a projected measure) leaves it None,
because at that point the type genuinely is not known. Carried for the same
reason :class:NestedField carries one: a dialect sometimes has to know
whether it is looking at a number, and the compiler models no types over
expression bodies.
It is excluded from equality and hashing. Structural comparison of
expressions is load-bearing in the planner - cfl_projection,
grain_dedup, filter_wrap, total_wrap and the three wrappers all
compare an expression against a freshly built one - and a ref that came
through the funnel would otherwise stop matching a hand-built one naming the
same column. That failure would be silent and would change results, which is
a worse defect than any this field exists to fix.
Source code in src/orionbelt/ast/nodes.py
orionbelt.ast.nodes.FunctionCall
dataclass
¶
SQL function call, e.g. SUM(col), DATE_TRUNC('month', col).
Source code in src/orionbelt/ast/nodes.py
orionbelt.ast.nodes.BinaryOp
dataclass
¶
orionbelt.ast.nodes.Literal
dataclass
¶
A literal value: number, string, boolean, or NULL.
Source code in src/orionbelt/ast/nodes.py
AST Builder¶
orionbelt.ast.builder.QueryBuilder
¶
Fluent builder for ergonomic AST construction.
Source code in src/orionbelt/ast/builder.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
unnest(node)
¶
Append an unnest in path order, alongside the joins.
Its parent has to already be in scope - the base object, or an earlier join - because the fragment names it.