altimate-code 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/bin/altimate +6 -0
- package/bin/altimate-code +6 -0
- package/dbt-tools/bin/altimate-dbt +2 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/__init__.py +0 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/fetch_schema.py +35 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/utils.py +353 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/altimate/validate_sql.py +114 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/__init__.py +178 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/__main__.py +96 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/_typing.py +17 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/__init__.py +3 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/__init__.py +18 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/_typing.py +18 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/column.py +332 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/dataframe.py +866 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/functions.py +1267 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/group.py +59 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/normalize.py +78 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/operations.py +53 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/readwriter.py +108 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/session.py +190 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/transforms.py +9 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/types.py +212 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/util.py +32 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dataframe/sql/window.py +134 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/__init__.py +118 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/athena.py +166 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/bigquery.py +1331 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/clickhouse.py +1393 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/databricks.py +131 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/dialect.py +1915 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/doris.py +561 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/drill.py +157 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/druid.py +20 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/duckdb.py +1159 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/dune.py +16 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/hive.py +787 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/materialize.py +94 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/mysql.py +1324 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/oracle.py +378 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/postgres.py +778 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/presto.py +788 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/prql.py +203 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/redshift.py +448 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/risingwave.py +78 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/snowflake.py +1464 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/spark.py +202 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/spark2.py +349 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/sqlite.py +320 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/starrocks.py +343 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/tableau.py +61 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/teradata.py +356 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/trino.py +115 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/dialects/tsql.py +1403 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/diff.py +456 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/errors.py +93 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/__init__.py +95 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/context.py +101 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/env.py +246 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/python.py +460 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/executor/table.py +155 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/expressions.py +8870 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/generator.py +4993 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/helper.py +582 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/jsonpath.py +227 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/lineage.py +423 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/__init__.py +11 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/annotate_types.py +589 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/canonicalize.py +222 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_ctes.py +43 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_joins.py +181 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/eliminate_subqueries.py +189 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/isolate_table_selects.py +50 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/merge_subqueries.py +415 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/normalize.py +200 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/normalize_identifiers.py +64 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/optimize_joins.py +91 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/optimizer.py +94 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/pushdown_predicates.py +222 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/pushdown_projections.py +172 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify.py +104 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify_columns.py +1024 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/qualify_tables.py +155 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/scope.py +904 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/simplify.py +1587 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/optimizer/unnest_subqueries.py +302 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/parser.py +8501 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/planner.py +463 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/schema.py +588 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/serde.py +68 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/time.py +687 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/tokens.py +1520 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/transforms.py +1020 -0
- package/dbt-tools/dist/altimate_python_packages/altimate_packages/sqlglot/trie.py +81 -0
- package/dbt-tools/dist/altimate_python_packages/dbt_core_integration.py +825 -0
- package/dbt-tools/dist/altimate_python_packages/dbt_utils.py +157 -0
- package/dbt-tools/dist/index.js +23859 -0
- package/package.json +14 -18
- package/postinstall.mjs +42 -0
- package/skills/altimate-setup/SKILL.md +31 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from sqlglot import alias, exp
|
|
7
|
+
from sqlglot.helper import name_sequence
|
|
8
|
+
from sqlglot.optimizer.eliminate_joins import join_condition
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Plan:
|
|
12
|
+
def __init__(self, expression: exp.Expression) -> None:
|
|
13
|
+
self.expression = expression.copy()
|
|
14
|
+
self.root = Step.from_expression(self.expression)
|
|
15
|
+
self._dag: t.Dict[Step, t.Set[Step]] = {}
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def dag(self) -> t.Dict[Step, t.Set[Step]]:
|
|
19
|
+
if not self._dag:
|
|
20
|
+
dag: t.Dict[Step, t.Set[Step]] = {}
|
|
21
|
+
nodes = {self.root}
|
|
22
|
+
|
|
23
|
+
while nodes:
|
|
24
|
+
node = nodes.pop()
|
|
25
|
+
dag[node] = set()
|
|
26
|
+
|
|
27
|
+
for dep in node.dependencies:
|
|
28
|
+
dag[node].add(dep)
|
|
29
|
+
nodes.add(dep)
|
|
30
|
+
|
|
31
|
+
self._dag = dag
|
|
32
|
+
|
|
33
|
+
return self._dag
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def leaves(self) -> t.Iterator[Step]:
|
|
37
|
+
return (node for node, deps in self.dag.items() if not deps)
|
|
38
|
+
|
|
39
|
+
def __repr__(self) -> str:
|
|
40
|
+
return f"Plan\n----\n{repr(self.root)}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Step:
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_expression(
|
|
46
|
+
cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
|
|
47
|
+
) -> Step:
|
|
48
|
+
"""
|
|
49
|
+
Builds a DAG of Steps from a SQL expression so that it's easier to execute in an engine.
|
|
50
|
+
Note: the expression's tables and subqueries must be aliased for this method to work. For
|
|
51
|
+
example, given the following expression:
|
|
52
|
+
|
|
53
|
+
SELECT
|
|
54
|
+
x.a,
|
|
55
|
+
SUM(x.b)
|
|
56
|
+
FROM x AS x
|
|
57
|
+
JOIN y AS y
|
|
58
|
+
ON x.a = y.a
|
|
59
|
+
GROUP BY x.a
|
|
60
|
+
|
|
61
|
+
the following DAG is produced (the expression IDs might differ per execution):
|
|
62
|
+
|
|
63
|
+
- Aggregate: x (4347984624)
|
|
64
|
+
Context:
|
|
65
|
+
Aggregations:
|
|
66
|
+
- SUM(x.b)
|
|
67
|
+
Group:
|
|
68
|
+
- x.a
|
|
69
|
+
Projections:
|
|
70
|
+
- x.a
|
|
71
|
+
- "x".""
|
|
72
|
+
Dependencies:
|
|
73
|
+
- Join: x (4347985296)
|
|
74
|
+
Context:
|
|
75
|
+
y:
|
|
76
|
+
On: x.a = y.a
|
|
77
|
+
Projections:
|
|
78
|
+
Dependencies:
|
|
79
|
+
- Scan: x (4347983136)
|
|
80
|
+
Context:
|
|
81
|
+
Source: x AS x
|
|
82
|
+
Projections:
|
|
83
|
+
- Scan: y (4343416624)
|
|
84
|
+
Context:
|
|
85
|
+
Source: y AS y
|
|
86
|
+
Projections:
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
expression: the expression to build the DAG from.
|
|
90
|
+
ctes: a dictionary that maps CTEs to their corresponding Step DAG by name.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A Step DAG corresponding to `expression`.
|
|
94
|
+
"""
|
|
95
|
+
ctes = ctes or {}
|
|
96
|
+
expression = expression.unnest()
|
|
97
|
+
with_ = expression.args.get("with")
|
|
98
|
+
|
|
99
|
+
# CTEs break the mold of scope and introduce themselves to all in the context.
|
|
100
|
+
if with_:
|
|
101
|
+
ctes = ctes.copy()
|
|
102
|
+
for cte in with_.expressions:
|
|
103
|
+
step = Step.from_expression(cte.this, ctes)
|
|
104
|
+
step.name = cte.alias
|
|
105
|
+
ctes[step.name] = step # type: ignore
|
|
106
|
+
|
|
107
|
+
from_ = expression.args.get("from")
|
|
108
|
+
|
|
109
|
+
if isinstance(expression, exp.Select) and from_:
|
|
110
|
+
step = Scan.from_expression(from_.this, ctes)
|
|
111
|
+
elif isinstance(expression, exp.SetOperation):
|
|
112
|
+
step = SetOperation.from_expression(expression, ctes)
|
|
113
|
+
else:
|
|
114
|
+
step = Scan()
|
|
115
|
+
|
|
116
|
+
joins = expression.args.get("joins")
|
|
117
|
+
|
|
118
|
+
if joins:
|
|
119
|
+
join = Join.from_joins(joins, ctes)
|
|
120
|
+
join.name = step.name
|
|
121
|
+
join.source_name = step.name
|
|
122
|
+
join.add_dependency(step)
|
|
123
|
+
step = join
|
|
124
|
+
|
|
125
|
+
projections = [] # final selects in this chain of steps representing a select
|
|
126
|
+
operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1)
|
|
127
|
+
aggregations = {}
|
|
128
|
+
next_operand_name = name_sequence("_a_")
|
|
129
|
+
|
|
130
|
+
def extract_agg_operands(expression):
|
|
131
|
+
agg_funcs = tuple(expression.find_all(exp.AggFunc))
|
|
132
|
+
if agg_funcs:
|
|
133
|
+
aggregations[expression] = None
|
|
134
|
+
|
|
135
|
+
for agg in agg_funcs:
|
|
136
|
+
for operand in agg.unnest_operands():
|
|
137
|
+
if isinstance(operand, exp.Column):
|
|
138
|
+
continue
|
|
139
|
+
if operand not in operands:
|
|
140
|
+
operands[operand] = next_operand_name()
|
|
141
|
+
|
|
142
|
+
operand.replace(exp.column(operands[operand], quoted=True))
|
|
143
|
+
|
|
144
|
+
return bool(agg_funcs)
|
|
145
|
+
|
|
146
|
+
def set_ops_and_aggs(step):
|
|
147
|
+
step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items())
|
|
148
|
+
step.aggregations = list(aggregations)
|
|
149
|
+
|
|
150
|
+
for e in expression.expressions:
|
|
151
|
+
if e.find(exp.AggFunc):
|
|
152
|
+
projections.append(exp.column(e.alias_or_name, step.name, quoted=True))
|
|
153
|
+
extract_agg_operands(e)
|
|
154
|
+
else:
|
|
155
|
+
projections.append(e)
|
|
156
|
+
|
|
157
|
+
where = expression.args.get("where")
|
|
158
|
+
|
|
159
|
+
if where:
|
|
160
|
+
step.condition = where.this
|
|
161
|
+
|
|
162
|
+
group = expression.args.get("group")
|
|
163
|
+
|
|
164
|
+
if group or aggregations:
|
|
165
|
+
aggregate = Aggregate()
|
|
166
|
+
aggregate.source = step.name
|
|
167
|
+
aggregate.name = step.name
|
|
168
|
+
|
|
169
|
+
having = expression.args.get("having")
|
|
170
|
+
|
|
171
|
+
if having:
|
|
172
|
+
if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)):
|
|
173
|
+
aggregate.condition = exp.column("_h", step.name, quoted=True)
|
|
174
|
+
else:
|
|
175
|
+
aggregate.condition = having.this
|
|
176
|
+
|
|
177
|
+
set_ops_and_aggs(aggregate)
|
|
178
|
+
|
|
179
|
+
# give aggregates names and replace projections with references to them
|
|
180
|
+
aggregate.group = {
|
|
181
|
+
f"_g{i}": e for i, e in enumerate(group.expressions if group else [])
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
intermediate: t.Dict[str | exp.Expression, str] = {}
|
|
185
|
+
for k, v in aggregate.group.items():
|
|
186
|
+
intermediate[v] = k
|
|
187
|
+
if isinstance(v, exp.Column):
|
|
188
|
+
intermediate[v.name] = k
|
|
189
|
+
|
|
190
|
+
for projection in projections:
|
|
191
|
+
for node in projection.walk():
|
|
192
|
+
name = intermediate.get(node)
|
|
193
|
+
if name:
|
|
194
|
+
node.replace(exp.column(name, step.name))
|
|
195
|
+
|
|
196
|
+
if aggregate.condition:
|
|
197
|
+
for node in aggregate.condition.walk():
|
|
198
|
+
name = intermediate.get(node) or intermediate.get(node.name)
|
|
199
|
+
if name:
|
|
200
|
+
node.replace(exp.column(name, step.name))
|
|
201
|
+
|
|
202
|
+
aggregate.add_dependency(step)
|
|
203
|
+
step = aggregate
|
|
204
|
+
else:
|
|
205
|
+
aggregate = None
|
|
206
|
+
|
|
207
|
+
order = expression.args.get("order")
|
|
208
|
+
|
|
209
|
+
if order:
|
|
210
|
+
if aggregate and isinstance(step, Aggregate):
|
|
211
|
+
for i, ordered in enumerate(order.expressions):
|
|
212
|
+
if extract_agg_operands(exp.alias_(ordered.this, f"_o_{i}", quoted=True)):
|
|
213
|
+
ordered.this.replace(exp.column(f"_o_{i}", step.name, quoted=True))
|
|
214
|
+
|
|
215
|
+
set_ops_and_aggs(aggregate)
|
|
216
|
+
|
|
217
|
+
sort = Sort()
|
|
218
|
+
sort.name = step.name
|
|
219
|
+
sort.key = order.expressions
|
|
220
|
+
sort.add_dependency(step)
|
|
221
|
+
step = sort
|
|
222
|
+
|
|
223
|
+
step.projections = projections
|
|
224
|
+
|
|
225
|
+
if isinstance(expression, exp.Select) and expression.args.get("distinct"):
|
|
226
|
+
distinct = Aggregate()
|
|
227
|
+
distinct.source = step.name
|
|
228
|
+
distinct.name = step.name
|
|
229
|
+
distinct.group = {
|
|
230
|
+
e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name)
|
|
231
|
+
for e in projections or expression.expressions
|
|
232
|
+
}
|
|
233
|
+
distinct.add_dependency(step)
|
|
234
|
+
step = distinct
|
|
235
|
+
|
|
236
|
+
limit = expression.args.get("limit")
|
|
237
|
+
|
|
238
|
+
if limit:
|
|
239
|
+
step.limit = int(limit.text("expression"))
|
|
240
|
+
|
|
241
|
+
return step
|
|
242
|
+
|
|
243
|
+
def __init__(self) -> None:
|
|
244
|
+
self.name: t.Optional[str] = None
|
|
245
|
+
self.dependencies: t.Set[Step] = set()
|
|
246
|
+
self.dependents: t.Set[Step] = set()
|
|
247
|
+
self.projections: t.Sequence[exp.Expression] = []
|
|
248
|
+
self.limit: float = math.inf
|
|
249
|
+
self.condition: t.Optional[exp.Expression] = None
|
|
250
|
+
|
|
251
|
+
def add_dependency(self, dependency: Step) -> None:
|
|
252
|
+
self.dependencies.add(dependency)
|
|
253
|
+
dependency.dependents.add(self)
|
|
254
|
+
|
|
255
|
+
def __repr__(self) -> str:
|
|
256
|
+
return self.to_s()
|
|
257
|
+
|
|
258
|
+
def to_s(self, level: int = 0) -> str:
|
|
259
|
+
indent = " " * level
|
|
260
|
+
nested = f"{indent} "
|
|
261
|
+
|
|
262
|
+
context = self._to_s(f"{nested} ")
|
|
263
|
+
|
|
264
|
+
if context:
|
|
265
|
+
context = [f"{nested}Context:"] + context
|
|
266
|
+
|
|
267
|
+
lines = [
|
|
268
|
+
f"{indent}- {self.id}",
|
|
269
|
+
*context,
|
|
270
|
+
f"{nested}Projections:",
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
for expression in self.projections:
|
|
274
|
+
lines.append(f"{nested} - {expression.sql()}")
|
|
275
|
+
|
|
276
|
+
if self.condition:
|
|
277
|
+
lines.append(f"{nested}Condition: {self.condition.sql()}")
|
|
278
|
+
|
|
279
|
+
if self.limit is not math.inf:
|
|
280
|
+
lines.append(f"{nested}Limit: {self.limit}")
|
|
281
|
+
|
|
282
|
+
if self.dependencies:
|
|
283
|
+
lines.append(f"{nested}Dependencies:")
|
|
284
|
+
for dependency in self.dependencies:
|
|
285
|
+
lines.append(" " + dependency.to_s(level + 1))
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def type_name(self) -> str:
|
|
291
|
+
return self.__class__.__name__
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def id(self) -> str:
|
|
295
|
+
name = self.name
|
|
296
|
+
name = f" {name}" if name else ""
|
|
297
|
+
return f"{self.type_name}:{name} ({id(self)})"
|
|
298
|
+
|
|
299
|
+
def _to_s(self, _indent: str) -> t.List[str]:
|
|
300
|
+
return []
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class Scan(Step):
|
|
304
|
+
@classmethod
|
|
305
|
+
def from_expression(
|
|
306
|
+
cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
|
|
307
|
+
) -> Step:
|
|
308
|
+
table = expression
|
|
309
|
+
alias_ = expression.alias_or_name
|
|
310
|
+
|
|
311
|
+
if isinstance(expression, exp.Subquery):
|
|
312
|
+
table = expression.this
|
|
313
|
+
step = Step.from_expression(table, ctes)
|
|
314
|
+
step.name = alias_
|
|
315
|
+
return step
|
|
316
|
+
|
|
317
|
+
step = Scan()
|
|
318
|
+
step.name = alias_
|
|
319
|
+
step.source = expression
|
|
320
|
+
if ctes and table.name in ctes:
|
|
321
|
+
step.add_dependency(ctes[table.name])
|
|
322
|
+
|
|
323
|
+
return step
|
|
324
|
+
|
|
325
|
+
def __init__(self) -> None:
|
|
326
|
+
super().__init__()
|
|
327
|
+
self.source: t.Optional[exp.Expression] = None
|
|
328
|
+
|
|
329
|
+
def _to_s(self, indent: str) -> t.List[str]:
|
|
330
|
+
return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"] # type: ignore
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class Join(Step):
|
|
334
|
+
@classmethod
|
|
335
|
+
def from_joins(
|
|
336
|
+
cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None
|
|
337
|
+
) -> Join:
|
|
338
|
+
step = Join()
|
|
339
|
+
|
|
340
|
+
for join in joins:
|
|
341
|
+
source_key, join_key, condition = join_condition(join)
|
|
342
|
+
step.joins[join.alias_or_name] = {
|
|
343
|
+
"side": join.side, # type: ignore
|
|
344
|
+
"join_key": join_key,
|
|
345
|
+
"source_key": source_key,
|
|
346
|
+
"condition": condition,
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
step.add_dependency(Scan.from_expression(join.this, ctes))
|
|
350
|
+
|
|
351
|
+
return step
|
|
352
|
+
|
|
353
|
+
def __init__(self) -> None:
|
|
354
|
+
super().__init__()
|
|
355
|
+
self.source_name: t.Optional[str] = None
|
|
356
|
+
self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {}
|
|
357
|
+
|
|
358
|
+
def _to_s(self, indent: str) -> t.List[str]:
|
|
359
|
+
lines = [f"{indent}Source: {self.source_name or self.name}"]
|
|
360
|
+
for name, join in self.joins.items():
|
|
361
|
+
lines.append(f"{indent}{name}: {join['side'] or 'INNER'}")
|
|
362
|
+
join_key = ", ".join(str(key) for key in t.cast(list, join.get("join_key") or []))
|
|
363
|
+
if join_key:
|
|
364
|
+
lines.append(f"{indent}Key: {join_key}")
|
|
365
|
+
if join.get("condition"):
|
|
366
|
+
lines.append(f"{indent}On: {join['condition'].sql()}") # type: ignore
|
|
367
|
+
return lines
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class Aggregate(Step):
|
|
371
|
+
def __init__(self) -> None:
|
|
372
|
+
super().__init__()
|
|
373
|
+
self.aggregations: t.List[exp.Expression] = []
|
|
374
|
+
self.operands: t.Tuple[exp.Expression, ...] = ()
|
|
375
|
+
self.group: t.Dict[str, exp.Expression] = {}
|
|
376
|
+
self.source: t.Optional[str] = None
|
|
377
|
+
|
|
378
|
+
def _to_s(self, indent: str) -> t.List[str]:
|
|
379
|
+
lines = [f"{indent}Aggregations:"]
|
|
380
|
+
|
|
381
|
+
for expression in self.aggregations:
|
|
382
|
+
lines.append(f"{indent} - {expression.sql()}")
|
|
383
|
+
|
|
384
|
+
if self.group:
|
|
385
|
+
lines.append(f"{indent}Group:")
|
|
386
|
+
for expression in self.group.values():
|
|
387
|
+
lines.append(f"{indent} - {expression.sql()}")
|
|
388
|
+
if self.condition:
|
|
389
|
+
lines.append(f"{indent}Having:")
|
|
390
|
+
lines.append(f"{indent} - {self.condition.sql()}")
|
|
391
|
+
if self.operands:
|
|
392
|
+
lines.append(f"{indent}Operands:")
|
|
393
|
+
for expression in self.operands:
|
|
394
|
+
lines.append(f"{indent} - {expression.sql()}")
|
|
395
|
+
|
|
396
|
+
return lines
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class Sort(Step):
|
|
400
|
+
def __init__(self) -> None:
|
|
401
|
+
super().__init__()
|
|
402
|
+
self.key = None
|
|
403
|
+
|
|
404
|
+
def _to_s(self, indent: str) -> t.List[str]:
|
|
405
|
+
lines = [f"{indent}Key:"]
|
|
406
|
+
|
|
407
|
+
for expression in self.key: # type: ignore
|
|
408
|
+
lines.append(f"{indent} - {expression.sql()}")
|
|
409
|
+
|
|
410
|
+
return lines
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class SetOperation(Step):
|
|
414
|
+
def __init__(
|
|
415
|
+
self,
|
|
416
|
+
op: t.Type[exp.Expression],
|
|
417
|
+
left: str | None,
|
|
418
|
+
right: str | None,
|
|
419
|
+
distinct: bool = False,
|
|
420
|
+
) -> None:
|
|
421
|
+
super().__init__()
|
|
422
|
+
self.op = op
|
|
423
|
+
self.left = left
|
|
424
|
+
self.right = right
|
|
425
|
+
self.distinct = distinct
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def from_expression(
|
|
429
|
+
cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None
|
|
430
|
+
) -> SetOperation:
|
|
431
|
+
assert isinstance(expression, exp.SetOperation)
|
|
432
|
+
|
|
433
|
+
left = Step.from_expression(expression.left, ctes)
|
|
434
|
+
# SELECT 1 UNION SELECT 2 <-- these subqueries don't have names
|
|
435
|
+
left.name = left.name or "left"
|
|
436
|
+
right = Step.from_expression(expression.right, ctes)
|
|
437
|
+
right.name = right.name or "right"
|
|
438
|
+
step = cls(
|
|
439
|
+
op=expression.__class__,
|
|
440
|
+
left=left.name,
|
|
441
|
+
right=right.name,
|
|
442
|
+
distinct=bool(expression.args.get("distinct")),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
step.add_dependency(left)
|
|
446
|
+
step.add_dependency(right)
|
|
447
|
+
|
|
448
|
+
limit = expression.args.get("limit")
|
|
449
|
+
|
|
450
|
+
if limit:
|
|
451
|
+
step.limit = int(limit.text("expression"))
|
|
452
|
+
|
|
453
|
+
return step
|
|
454
|
+
|
|
455
|
+
def _to_s(self, indent: str) -> t.List[str]:
|
|
456
|
+
lines = []
|
|
457
|
+
if self.distinct:
|
|
458
|
+
lines.append(f"{indent}Distinct: {self.distinct}")
|
|
459
|
+
return lines
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def type_name(self) -> str:
|
|
463
|
+
return self.op.__name__
|