bqsqlparse 0.1.0__py3-none-any.whl
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.
- bqsqlparse/__init__.py +62 -0
- bqsqlparse/errors.py +30 -0
- bqsqlparse/functions.py +159 -0
- bqsqlparse/lexer.py +242 -0
- bqsqlparse/lineage.py +902 -0
- bqsqlparse/nodes.py +728 -0
- bqsqlparse/parser.py +1715 -0
- bqsqlparse/py.typed +0 -0
- bqsqlparse/unparse.py +766 -0
- bqsqlparse-0.1.0.dist-info/METADATA +722 -0
- bqsqlparse-0.1.0.dist-info/RECORD +13 -0
- bqsqlparse-0.1.0.dist-info/WHEEL +4 -0
- bqsqlparse-0.1.0.dist-info/licenses/LICENSE +21 -0
bqsqlparse/unparse.py
ADDED
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
"""SQL generation (AST -> BigQuery SQL text)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import singledispatch
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from . import nodes as n
|
|
9
|
+
from .lexer import RESERVED
|
|
10
|
+
|
|
11
|
+
_SAFE_IDENT_CHARS = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _ident(name: str) -> str:
|
|
15
|
+
if (
|
|
16
|
+
name
|
|
17
|
+
and all(c in _SAFE_IDENT_CHARS for c in name)
|
|
18
|
+
and not name[0].isdigit()
|
|
19
|
+
and name.upper() not in RESERVED
|
|
20
|
+
):
|
|
21
|
+
return name
|
|
22
|
+
return f"`{name}`"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _path(parts: List[str]) -> str:
|
|
26
|
+
return ".".join(_ident(p) for p in parts)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _str_lit(value: str) -> str:
|
|
30
|
+
escaped = value.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n")
|
|
31
|
+
return f"'{escaped}'"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_sql(node: "n.Node") -> str:
|
|
35
|
+
"""Render an AST node back to BigQuery SQL."""
|
|
36
|
+
return _sql(node)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@singledispatch
|
|
40
|
+
def _sql(node) -> str:
|
|
41
|
+
raise TypeError(f"Cannot generate SQL for {type(node).__name__}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------- literals
|
|
45
|
+
|
|
46
|
+
@_sql.register(n.Literal)
|
|
47
|
+
def _(node: n.Literal) -> str:
|
|
48
|
+
if node.kind == "null":
|
|
49
|
+
return "NULL"
|
|
50
|
+
if node.kind == "default":
|
|
51
|
+
return "DEFAULT"
|
|
52
|
+
if node.kind == "bool":
|
|
53
|
+
return "TRUE" if node.value else "FALSE"
|
|
54
|
+
if node.kind == "number":
|
|
55
|
+
return str(node.value)
|
|
56
|
+
if node.kind == "bytes":
|
|
57
|
+
return "b" + _str_lit(str(node.value))
|
|
58
|
+
return _str_lit(str(node.value))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@_sql.register(n.TypedLiteral)
|
|
62
|
+
def _(node: n.TypedLiteral) -> str:
|
|
63
|
+
return f"{node.type_name} {_str_lit(node.value)}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@_sql.register(n.ColumnRef)
|
|
67
|
+
def _(node: n.ColumnRef) -> str:
|
|
68
|
+
return _path(node.path)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@_sql.register(n.Param)
|
|
72
|
+
def _(node: n.Param) -> str:
|
|
73
|
+
return node.name
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@_sql.register(n.Star)
|
|
77
|
+
def _(node: n.Star) -> str:
|
|
78
|
+
out = (_path(node.prefix) + "." if node.prefix else "") + "*"
|
|
79
|
+
if node.except_:
|
|
80
|
+
out += f" EXCEPT ({', '.join(_ident(c) for c in node.except_)})"
|
|
81
|
+
if node.replace:
|
|
82
|
+
items = ", ".join(f"{_sql(r.expr)} AS {_ident(r.name)}" for r in node.replace)
|
|
83
|
+
out += f" REPLACE ({items})"
|
|
84
|
+
return out
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------- expressions
|
|
88
|
+
|
|
89
|
+
@_sql.register(n.BinaryOp)
|
|
90
|
+
def _(node: n.BinaryOp) -> str:
|
|
91
|
+
return f"{_sql(node.left)} {node.op} {_sql(node.right)}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@_sql.register(n.UnaryOp)
|
|
95
|
+
def _(node: n.UnaryOp) -> str:
|
|
96
|
+
if node.op == "NOT":
|
|
97
|
+
return f"NOT {_sql(node.operand)}"
|
|
98
|
+
return f"{node.op}{_sql(node.operand)}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@_sql.register(n.Between)
|
|
102
|
+
def _(node: n.Between) -> str:
|
|
103
|
+
neg = "NOT " if node.negated else ""
|
|
104
|
+
return f"{_sql(node.expr)} {neg}BETWEEN {_sql(node.low)} AND {_sql(node.high)}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@_sql.register(n.InExpr)
|
|
108
|
+
def _(node: n.InExpr) -> str:
|
|
109
|
+
neg = "NOT " if node.negated else ""
|
|
110
|
+
if node.unnest is not None:
|
|
111
|
+
return f"{_sql(node.expr)} {neg}IN UNNEST({_sql(node.unnest)})"
|
|
112
|
+
if node.query is not None:
|
|
113
|
+
return f"{_sql(node.expr)} {neg}IN ({_sql(node.query)})"
|
|
114
|
+
vals = ", ".join(_sql(v) for v in node.values or [])
|
|
115
|
+
return f"{_sql(node.expr)} {neg}IN ({vals})"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@_sql.register(n.LikeExpr)
|
|
119
|
+
def _(node: n.LikeExpr) -> str:
|
|
120
|
+
neg = "NOT " if node.negated else ""
|
|
121
|
+
if node.quantifier:
|
|
122
|
+
pats = ", ".join(_sql(p) for p in node.patterns)
|
|
123
|
+
return f"{_sql(node.expr)} {neg}LIKE {node.quantifier} ({pats})"
|
|
124
|
+
return f"{_sql(node.expr)} {neg}LIKE {_sql(node.patterns[0])}"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@_sql.register(n.IsExpr)
|
|
128
|
+
def _(node: n.IsExpr) -> str:
|
|
129
|
+
neg = "NOT " if node.negated else ""
|
|
130
|
+
return f"{_sql(node.expr)} IS {neg}{node.value}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@_sql.register(n.Case)
|
|
134
|
+
def _(node: n.Case) -> str:
|
|
135
|
+
parts = ["CASE"]
|
|
136
|
+
if node.operand is not None:
|
|
137
|
+
parts.append(_sql(node.operand))
|
|
138
|
+
for w in node.whens:
|
|
139
|
+
parts.append(f"WHEN {_sql(w.condition)} THEN {_sql(w.result)}")
|
|
140
|
+
if node.else_ is not None:
|
|
141
|
+
parts.append(f"ELSE {_sql(node.else_)}")
|
|
142
|
+
parts.append("END")
|
|
143
|
+
return " ".join(parts)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@_sql.register(n.Cast)
|
|
147
|
+
def _(node: n.Cast) -> str:
|
|
148
|
+
fn = "SAFE_CAST" if node.safe else "CAST"
|
|
149
|
+
fmt = f" FORMAT {_sql(node.format)}" if node.format is not None else ""
|
|
150
|
+
return f"{fn}({_sql(node.expr)} AS {_sql(node.to_type)}{fmt})"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@_sql.register(n.Extract)
|
|
154
|
+
def _(node: n.Extract) -> str:
|
|
155
|
+
tz = f" AT TIME ZONE {_sql(node.at_time_zone)}" if node.at_time_zone is not None else ""
|
|
156
|
+
return f"EXTRACT({node.part} FROM {_sql(node.expr)}{tz})"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@_sql.register(n.IntervalExpr)
|
|
160
|
+
def _(node: n.IntervalExpr) -> str:
|
|
161
|
+
to = f" TO {node.to_unit}" if node.to_unit else ""
|
|
162
|
+
return f"INTERVAL {_sql(node.value)} {node.unit}{to}"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@_sql.register(n.ArrayExpr)
|
|
166
|
+
def _(node: n.ArrayExpr) -> str:
|
|
167
|
+
prefix = f"ARRAY<{_sql(node.elem_type)}>" if node.elem_type is not None else ""
|
|
168
|
+
return f"{prefix}[{', '.join(_sql(e) for e in node.elements)}]"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@_sql.register(n.StructExpr)
|
|
172
|
+
def _(node: n.StructExpr) -> str:
|
|
173
|
+
prefix = "STRUCT"
|
|
174
|
+
if node.type_fields is not None:
|
|
175
|
+
inner = ", ".join(
|
|
176
|
+
(f"{_ident(f.name)} " if f.name else "") + _sql(f.type)
|
|
177
|
+
for f in node.type_fields
|
|
178
|
+
)
|
|
179
|
+
prefix = f"STRUCT<{inner}>"
|
|
180
|
+
fields = ", ".join(
|
|
181
|
+
_sql(f.expr) + (f" AS {_ident(f.name)}" if f.name else "")
|
|
182
|
+
for f in node.fields_
|
|
183
|
+
)
|
|
184
|
+
if node.type_fields is None and prefix == "STRUCT":
|
|
185
|
+
return f"STRUCT({fields})"
|
|
186
|
+
return f"{prefix}({fields})"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@_sql.register(n.Subscript)
|
|
190
|
+
def _(node: n.Subscript) -> str:
|
|
191
|
+
if node.mode:
|
|
192
|
+
return f"{_sql(node.base)}[{node.mode}({_sql(node.index)})]"
|
|
193
|
+
return f"{_sql(node.base)}[{_sql(node.index)}]"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@_sql.register(n.FieldAccess)
|
|
197
|
+
def _(node: n.FieldAccess) -> str:
|
|
198
|
+
return f"{_sql(node.base)}.{_ident(node.field_name)}"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@_sql.register(n.NamedArg)
|
|
202
|
+
def _(node: n.NamedArg) -> str:
|
|
203
|
+
return f"{node.name} => {_sql(node.value)}"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@_sql.register(n.FuncCall)
|
|
207
|
+
def _(node: n.FuncCall) -> str:
|
|
208
|
+
name = ".".join(node.name)
|
|
209
|
+
if not node.args and not node.distinct and node.name_str in (
|
|
210
|
+
"CURRENT_DATE", "CURRENT_DATETIME", "CURRENT_TIME", "CURRENT_TIMESTAMP",
|
|
211
|
+
) and node.over is None:
|
|
212
|
+
return name
|
|
213
|
+
parts = []
|
|
214
|
+
if node.distinct:
|
|
215
|
+
parts.append("DISTINCT")
|
|
216
|
+
parts.append(", ".join(_sql(a) for a in node.args))
|
|
217
|
+
if node.nulls:
|
|
218
|
+
parts.append(f"{node.nulls} NULLS")
|
|
219
|
+
if node.having is not None:
|
|
220
|
+
parts.append(f"HAVING {node.having.kind} {_sql(node.having.expr)}")
|
|
221
|
+
if node.order_by:
|
|
222
|
+
parts.append("ORDER BY " + ", ".join(_sql(o) for o in node.order_by))
|
|
223
|
+
if node.limit is not None:
|
|
224
|
+
parts.append(f"LIMIT {_sql(node.limit)}")
|
|
225
|
+
body = " ".join(p for p in parts if p)
|
|
226
|
+
out = f"{name}({body})"
|
|
227
|
+
if node.over is not None:
|
|
228
|
+
if node.over.is_ref:
|
|
229
|
+
out += f" OVER {node.over.name}"
|
|
230
|
+
else:
|
|
231
|
+
out += f" OVER ({_window_body(node.over)})"
|
|
232
|
+
return out
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _window_body(spec: "n.WindowSpec") -> str:
|
|
236
|
+
parts = []
|
|
237
|
+
if spec.name:
|
|
238
|
+
parts.append(spec.name)
|
|
239
|
+
if spec.partition_by:
|
|
240
|
+
parts.append("PARTITION BY " + ", ".join(_sql(e) for e in spec.partition_by))
|
|
241
|
+
if spec.order_by:
|
|
242
|
+
parts.append("ORDER BY " + ", ".join(_sql(o) for o in spec.order_by))
|
|
243
|
+
if spec.frame is not None:
|
|
244
|
+
f = spec.frame
|
|
245
|
+
if f.end is not None:
|
|
246
|
+
parts.append(f"{f.unit} BETWEEN {_bound(f.start)} AND {_bound(f.end)}")
|
|
247
|
+
else:
|
|
248
|
+
parts.append(f"{f.unit} {_bound(f.start)}")
|
|
249
|
+
return " ".join(parts)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _bound(b: "n.FrameBound") -> str:
|
|
253
|
+
if b.value is not None:
|
|
254
|
+
return f"{_sql(b.value)} {b.kind}"
|
|
255
|
+
return b.kind
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@_sql.register(n.OrderItem)
|
|
259
|
+
def _(node: n.OrderItem) -> str:
|
|
260
|
+
out = _sql(node.expr)
|
|
261
|
+
if node.desc is True:
|
|
262
|
+
out += " DESC"
|
|
263
|
+
elif node.desc is False:
|
|
264
|
+
out += " ASC"
|
|
265
|
+
if node.nulls:
|
|
266
|
+
out += f" NULLS {node.nulls}"
|
|
267
|
+
return out
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@_sql.register(n.ScalarSubquery)
|
|
271
|
+
def _(node: n.ScalarSubquery) -> str:
|
|
272
|
+
return f"({_sql(node.query)})"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@_sql.register(n.ArraySubquery)
|
|
276
|
+
def _(node: n.ArraySubquery) -> str:
|
|
277
|
+
return f"ARRAY({_sql(node.query)})"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@_sql.register(n.ExistsSubquery)
|
|
281
|
+
def _(node: n.ExistsSubquery) -> str:
|
|
282
|
+
return f"EXISTS ({_sql(node.query)})"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------- types
|
|
286
|
+
|
|
287
|
+
@_sql.register(n.TypeNode)
|
|
288
|
+
def _(node: n.TypeNode) -> str:
|
|
289
|
+
if node.name == "ARRAY" and node.element is not None:
|
|
290
|
+
return f"ARRAY<{_sql(node.element)}>"
|
|
291
|
+
if node.name == "RANGE" and node.element is not None:
|
|
292
|
+
return f"RANGE<{_sql(node.element)}>"
|
|
293
|
+
if node.name == "STRUCT":
|
|
294
|
+
if not node.fields_:
|
|
295
|
+
return "STRUCT"
|
|
296
|
+
inner = ", ".join(
|
|
297
|
+
(f"{_ident(f.name)} " if f.name else "") + _sql(f.type)
|
|
298
|
+
for f in node.fields_
|
|
299
|
+
)
|
|
300
|
+
return f"STRUCT<{inner}>"
|
|
301
|
+
if node.params:
|
|
302
|
+
return f"{node.name}({', '.join(node.params)})"
|
|
303
|
+
return node.name
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ---------------------------------------------------------------- query parts
|
|
307
|
+
|
|
308
|
+
@_sql.register(n.SelectItem)
|
|
309
|
+
def _(node: n.SelectItem) -> str:
|
|
310
|
+
out = _sql(node.expr)
|
|
311
|
+
if node.alias:
|
|
312
|
+
out += f" AS {_ident(node.alias)}"
|
|
313
|
+
return out
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@_sql.register(n.Select)
|
|
317
|
+
def _(node: n.Select) -> str:
|
|
318
|
+
parts = ["SELECT"]
|
|
319
|
+
if node.distinct:
|
|
320
|
+
parts.append("DISTINCT")
|
|
321
|
+
if node.as_mode:
|
|
322
|
+
parts.append(f"AS {node.as_mode}")
|
|
323
|
+
parts.append(", ".join(_sql(i) for i in node.items))
|
|
324
|
+
if node.from_ is not None:
|
|
325
|
+
parts.append(f"FROM {_sql(node.from_)}")
|
|
326
|
+
if node.where is not None:
|
|
327
|
+
parts.append(f"WHERE {_sql(node.where)}")
|
|
328
|
+
if node.group_by is not None:
|
|
329
|
+
parts.append(_sql(node.group_by))
|
|
330
|
+
if node.having is not None:
|
|
331
|
+
parts.append(f"HAVING {_sql(node.having)}")
|
|
332
|
+
if node.qualify is not None:
|
|
333
|
+
parts.append(f"QUALIFY {_sql(node.qualify)}")
|
|
334
|
+
if node.windows:
|
|
335
|
+
wins = ", ".join(f"{w.name} AS ({_window_body(w.spec)})" for w in node.windows)
|
|
336
|
+
parts.append(f"WINDOW {wins}")
|
|
337
|
+
return " ".join(parts)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@_sql.register(n.GroupBy)
|
|
341
|
+
def _(node: n.GroupBy) -> str:
|
|
342
|
+
if node.kind == "all":
|
|
343
|
+
return "GROUP BY ALL"
|
|
344
|
+
if node.kind == "rollup":
|
|
345
|
+
return f"GROUP BY ROLLUP({', '.join(_sql(e) for e in node.exprs)})"
|
|
346
|
+
if node.kind == "cube":
|
|
347
|
+
return f"GROUP BY CUBE({', '.join(_sql(e) for e in node.exprs)})"
|
|
348
|
+
if node.kind == "sets":
|
|
349
|
+
sets = ", ".join("(" + ", ".join(_sql(e) for e in s) + ")" for s in node.sets)
|
|
350
|
+
return f"GROUP BY GROUPING SETS ({sets})"
|
|
351
|
+
return "GROUP BY " + ", ".join(_sql(e) for e in node.exprs)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@_sql.register(n.SetOp)
|
|
355
|
+
def _(node: n.SetOp) -> str:
|
|
356
|
+
mode = "ALL" if node.all else "DISTINCT"
|
|
357
|
+
return f"{_sql(node.left)} {node.op} {mode} {_sql(node.right)}"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@_sql.register(n.Query)
|
|
361
|
+
def _(node: n.Query) -> str:
|
|
362
|
+
parts = []
|
|
363
|
+
if node.ctes:
|
|
364
|
+
rec = "RECURSIVE " if node.recursive else ""
|
|
365
|
+
ctes = ", ".join(f"{_ident(c.name)} AS ({_sql(c.query)})" for c in node.ctes)
|
|
366
|
+
parts.append(f"WITH {rec}{ctes}")
|
|
367
|
+
body = _sql(node.body)
|
|
368
|
+
if isinstance(node.body, n.Query):
|
|
369
|
+
body = f"({body})"
|
|
370
|
+
parts.append(body)
|
|
371
|
+
if node.order_by:
|
|
372
|
+
parts.append("ORDER BY " + ", ".join(_sql(o) for o in node.order_by))
|
|
373
|
+
if node.limit is not None:
|
|
374
|
+
parts.append(f"LIMIT {_sql(node.limit)}")
|
|
375
|
+
if node.offset is not None:
|
|
376
|
+
parts.append(f"OFFSET {_sql(node.offset)}")
|
|
377
|
+
return " ".join(parts)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# ----------------------------------------------------------------- FROM items
|
|
381
|
+
|
|
382
|
+
@_sql.register(n.TableRef)
|
|
383
|
+
def _(node: n.TableRef) -> str:
|
|
384
|
+
out = _path(node.path)
|
|
385
|
+
if node.system_time is not None:
|
|
386
|
+
out += f" FOR SYSTEM_TIME AS OF {_sql(node.system_time)}"
|
|
387
|
+
if node.alias:
|
|
388
|
+
out += f" AS {_ident(node.alias)}"
|
|
389
|
+
if node.sample is not None:
|
|
390
|
+
out += f" TABLESAMPLE {node.sample.method} ({_sql(node.sample.value)} {node.sample.unit})"
|
|
391
|
+
return out
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@_sql.register(n.SubqueryRef)
|
|
395
|
+
def _(node: n.SubqueryRef) -> str:
|
|
396
|
+
out = f"({_sql(node.query)})"
|
|
397
|
+
if node.alias:
|
|
398
|
+
out += f" AS {_ident(node.alias)}"
|
|
399
|
+
return out
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@_sql.register(n.UnnestRef)
|
|
403
|
+
def _(node: n.UnnestRef) -> str:
|
|
404
|
+
out = f"UNNEST({_sql(node.expr)})"
|
|
405
|
+
if node.alias:
|
|
406
|
+
out += f" AS {_ident(node.alias)}"
|
|
407
|
+
if node.with_offset:
|
|
408
|
+
out += " WITH OFFSET"
|
|
409
|
+
if node.offset_alias:
|
|
410
|
+
out += f" AS {_ident(node.offset_alias)}"
|
|
411
|
+
return out
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@_sql.register(n.TableFuncRef)
|
|
415
|
+
def _(node: n.TableFuncRef) -> str:
|
|
416
|
+
out = f"{'.'.join(node.name)}({', '.join(_sql(a) for a in node.args)})"
|
|
417
|
+
if node.alias:
|
|
418
|
+
out += f" AS {_ident(node.alias)}"
|
|
419
|
+
return out
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@_sql.register(n.Join)
|
|
423
|
+
def _(node: n.Join) -> str:
|
|
424
|
+
if node.kind == "CROSS" and node.on is None and not node.using:
|
|
425
|
+
return f"{_sql(node.left)} CROSS JOIN {_sql(node.right)}"
|
|
426
|
+
kw = "JOIN" if node.kind == "INNER" else f"{node.kind} JOIN"
|
|
427
|
+
out = f"{_sql(node.left)} {kw} {_sql(node.right)}"
|
|
428
|
+
if node.on is not None:
|
|
429
|
+
out += f" ON {_sql(node.on)}"
|
|
430
|
+
elif node.using:
|
|
431
|
+
out += f" USING ({', '.join(_ident(c) for c in node.using)})"
|
|
432
|
+
return out
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@_sql.register(n.PivotRef)
|
|
436
|
+
def _(node: n.PivotRef) -> str:
|
|
437
|
+
aggs = ", ".join(
|
|
438
|
+
_sql(a.func) + (f" AS {_ident(a.alias)}" if a.alias else "")
|
|
439
|
+
for a in node.aggregates
|
|
440
|
+
)
|
|
441
|
+
vals = ", ".join(
|
|
442
|
+
_sql(v.value) + (f" AS {_ident(v.alias)}" if v.alias else "")
|
|
443
|
+
for v in node.in_values
|
|
444
|
+
)
|
|
445
|
+
out = f"{_sql(node.input)} PIVOT({aggs} FOR {_sql(node.for_col)} IN ({vals}))"
|
|
446
|
+
if node.alias:
|
|
447
|
+
out += f" AS {_ident(node.alias)}"
|
|
448
|
+
return out
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@_sql.register(n.UnpivotRef)
|
|
452
|
+
def _(node: n.UnpivotRef) -> str:
|
|
453
|
+
nulls = ""
|
|
454
|
+
if node.include_nulls is True:
|
|
455
|
+
nulls = " INCLUDE NULLS"
|
|
456
|
+
elif node.include_nulls is False:
|
|
457
|
+
nulls = " EXCLUDE NULLS"
|
|
458
|
+
multi = len(node.value_columns) > 1
|
|
459
|
+
vcols = "(" + ", ".join(node.value_columns) + ")" if multi else node.value_columns[0]
|
|
460
|
+
groups = []
|
|
461
|
+
for g in node.groups:
|
|
462
|
+
cols = ", ".join(_path(c) for c in g.columns)
|
|
463
|
+
if multi:
|
|
464
|
+
cols = f"({cols})"
|
|
465
|
+
if g.label is not None:
|
|
466
|
+
cols += f" AS {_sql(g.label)}"
|
|
467
|
+
groups.append(cols)
|
|
468
|
+
out = (f"{_sql(node.input)} UNPIVOT{nulls}({vcols} FOR {node.name_column} "
|
|
469
|
+
f"IN ({', '.join(groups)}))")
|
|
470
|
+
if node.alias:
|
|
471
|
+
out += f" AS {_ident(node.alias)}"
|
|
472
|
+
return out
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
# ----------------------------------------------------------------- statements
|
|
476
|
+
|
|
477
|
+
@_sql.register(n.CreateTableAsSelect)
|
|
478
|
+
def _(node: n.CreateTableAsSelect) -> str:
|
|
479
|
+
parts = ["CREATE"]
|
|
480
|
+
if node.replace:
|
|
481
|
+
parts.append("OR REPLACE")
|
|
482
|
+
if node.temp:
|
|
483
|
+
parts.append("TEMP")
|
|
484
|
+
parts.append(node.kind)
|
|
485
|
+
if node.if_not_exists:
|
|
486
|
+
parts.append("IF NOT EXISTS")
|
|
487
|
+
parts.append(_path(node.name))
|
|
488
|
+
if node.columns:
|
|
489
|
+
cols = ", ".join(
|
|
490
|
+
_ident(c.name) + (f" {_sql(c.type)}" if c.type is not None else "")
|
|
491
|
+
for c in node.columns
|
|
492
|
+
)
|
|
493
|
+
parts.append(f"({cols})")
|
|
494
|
+
if node.partition_by is not None:
|
|
495
|
+
parts.append(f"PARTITION BY {_sql(node.partition_by)}")
|
|
496
|
+
if node.cluster_by:
|
|
497
|
+
parts.append("CLUSTER BY " + ", ".join(_sql(e) for e in node.cluster_by))
|
|
498
|
+
if node.options_sql:
|
|
499
|
+
parts.append(f"OPTIONS{node.options_sql}")
|
|
500
|
+
if node.query is not None:
|
|
501
|
+
parts.append(f"AS {_sql(node.query)}")
|
|
502
|
+
return " ".join(parts)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@_sql.register(n.InsertStmt)
|
|
506
|
+
def _(node: n.InsertStmt) -> str:
|
|
507
|
+
parts = [f"INSERT INTO {_path(node.table)}"]
|
|
508
|
+
if node.columns:
|
|
509
|
+
parts.append("(" + ", ".join(_ident(c) for c in node.columns) + ")")
|
|
510
|
+
if node.values:
|
|
511
|
+
rows = ", ".join("(" + ", ".join(_sql(v) for v in row) + ")" for row in node.values)
|
|
512
|
+
parts.append(f"VALUES {rows}")
|
|
513
|
+
elif node.query is not None:
|
|
514
|
+
parts.append(_sql(node.query))
|
|
515
|
+
return " ".join(parts)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@_sql.register(n.UpdateStmt)
|
|
519
|
+
def _(node: n.UpdateStmt) -> str:
|
|
520
|
+
parts = [f"UPDATE {_path(node.table)}"]
|
|
521
|
+
if node.alias:
|
|
522
|
+
parts.append(f"AS {_ident(node.alias)}")
|
|
523
|
+
sets = ", ".join(f"{_path(a.target)} = {_sql(a.value)}" for a in node.assignments)
|
|
524
|
+
parts.append(f"SET {sets}")
|
|
525
|
+
if node.from_ is not None:
|
|
526
|
+
parts.append(f"FROM {_sql(node.from_)}")
|
|
527
|
+
if node.where is not None:
|
|
528
|
+
parts.append(f"WHERE {_sql(node.where)}")
|
|
529
|
+
return " ".join(parts)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@_sql.register(n.DeleteStmt)
|
|
533
|
+
def _(node: n.DeleteStmt) -> str:
|
|
534
|
+
parts = [f"DELETE FROM {_path(node.table)}"]
|
|
535
|
+
if node.alias:
|
|
536
|
+
parts.append(f"AS {_ident(node.alias)}")
|
|
537
|
+
if node.where is not None:
|
|
538
|
+
parts.append(f"WHERE {_sql(node.where)}")
|
|
539
|
+
return " ".join(parts)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@_sql.register(n.MergeStmt)
|
|
543
|
+
def _(node: n.MergeStmt) -> str:
|
|
544
|
+
parts = [f"MERGE INTO {_path(node.target)}"]
|
|
545
|
+
if node.alias:
|
|
546
|
+
parts.append(f"AS {_ident(node.alias)}")
|
|
547
|
+
parts.append(f"USING {_sql(node.source)}")
|
|
548
|
+
parts.append(f"ON {_sql(node.on)}")
|
|
549
|
+
for w in node.whens:
|
|
550
|
+
kind = {
|
|
551
|
+
"MATCHED": "WHEN MATCHED",
|
|
552
|
+
"NOT_MATCHED": "WHEN NOT MATCHED",
|
|
553
|
+
"NOT_MATCHED_BY_SOURCE": "WHEN NOT MATCHED BY SOURCE",
|
|
554
|
+
}[w.match_kind]
|
|
555
|
+
if w.condition is not None:
|
|
556
|
+
kind += f" AND {_sql(w.condition)}"
|
|
557
|
+
kind += " THEN "
|
|
558
|
+
if w.action_kind == "UPDATE":
|
|
559
|
+
sets = ", ".join(f"{_path(a.target)} = {_sql(a.value)}" for a in w.assignments)
|
|
560
|
+
kind += f"UPDATE SET {sets}"
|
|
561
|
+
elif w.action_kind == "DELETE":
|
|
562
|
+
kind += "DELETE"
|
|
563
|
+
elif w.action_kind == "INSERT_ROW":
|
|
564
|
+
kind += "INSERT ROW"
|
|
565
|
+
else:
|
|
566
|
+
cols = ""
|
|
567
|
+
if w.columns:
|
|
568
|
+
cols = "(" + ", ".join(_ident(c) for c in w.columns) + ") "
|
|
569
|
+
kind += f"INSERT {cols}VALUES (" + ", ".join(_sql(v) for v in w.values) + ")"
|
|
570
|
+
parts.append(kind)
|
|
571
|
+
return " ".join(parts)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
# ------------------------------------------------------------------ scripting
|
|
575
|
+
|
|
576
|
+
def _stmts(statements) -> str:
|
|
577
|
+
return " ".join(_sql(s) + ";" for s in statements)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
@_sql.register(n.DeclareStmt)
|
|
581
|
+
def _(node: n.DeclareStmt) -> str:
|
|
582
|
+
out = "DECLARE " + ", ".join(_ident(x) for x in node.names)
|
|
583
|
+
if node.type is not None:
|
|
584
|
+
out += f" {_sql(node.type)}"
|
|
585
|
+
if node.default is not None:
|
|
586
|
+
out += f" DEFAULT {_sql(node.default)}"
|
|
587
|
+
return out
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
@_sql.register(n.SetStmt)
|
|
591
|
+
def _(node: n.SetStmt) -> str:
|
|
592
|
+
if len(node.targets) > 1:
|
|
593
|
+
tgt = "(" + ", ".join(node.targets) + ")"
|
|
594
|
+
else:
|
|
595
|
+
tgt = node.targets[0] if node.targets[0].startswith("@") else _ident(node.targets[0])
|
|
596
|
+
return f"SET {tgt} = {_sql(node.value)}"
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
@_sql.register(n.ScriptBlock)
|
|
600
|
+
def _(node: n.ScriptBlock) -> str:
|
|
601
|
+
label = f"{node.label}: " if node.label else ""
|
|
602
|
+
out = f"{label}BEGIN {_stmts(node.statements)}"
|
|
603
|
+
if node.has_exception_handler:
|
|
604
|
+
out += f" EXCEPTION WHEN ERROR THEN {_stmts(node.exception_statements)}"
|
|
605
|
+
return out.rstrip() + " END"
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@_sql.register(n.IfStmt)
|
|
609
|
+
def _(node: n.IfStmt) -> str:
|
|
610
|
+
first = node.branches[0]
|
|
611
|
+
out = f"IF {_sql(first.condition)} THEN {_stmts(first.statements)}"
|
|
612
|
+
for b in node.branches[1:]:
|
|
613
|
+
out += f" ELSEIF {_sql(b.condition)} THEN {_stmts(b.statements)}"
|
|
614
|
+
if node.else_statements:
|
|
615
|
+
out += f" ELSE {_stmts(node.else_statements)}"
|
|
616
|
+
return out.rstrip() + " END IF"
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
@_sql.register(n.LoopStmt)
|
|
620
|
+
def _(node: n.LoopStmt) -> str:
|
|
621
|
+
label = f"{node.label}: " if node.label else ""
|
|
622
|
+
return f"{label}LOOP {_stmts(node.statements)}".rstrip() + " END LOOP"
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
@_sql.register(n.WhileStmt)
|
|
626
|
+
def _(node: n.WhileStmt) -> str:
|
|
627
|
+
label = f"{node.label}: " if node.label else ""
|
|
628
|
+
return (f"{label}WHILE {_sql(node.condition)} DO "
|
|
629
|
+
f"{_stmts(node.statements)}").rstrip() + " END WHILE"
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@_sql.register(n.RepeatStmt)
|
|
633
|
+
def _(node: n.RepeatStmt) -> str:
|
|
634
|
+
label = f"{node.label}: " if node.label else ""
|
|
635
|
+
return (f"{label}REPEAT {_stmts(node.statements)} "
|
|
636
|
+
f"UNTIL {_sql(node.until)} END REPEAT")
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@_sql.register(n.ForInStmt)
|
|
640
|
+
def _(node: n.ForInStmt) -> str:
|
|
641
|
+
label = f"{node.label}: " if node.label else ""
|
|
642
|
+
return (f"{label}FOR {_ident(node.var)} IN ({_sql(node.query)}) DO "
|
|
643
|
+
f"{_stmts(node.statements)}").rstrip() + " END FOR"
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
@_sql.register(n.BreakContinueStmt)
|
|
647
|
+
def _(node: n.BreakContinueStmt) -> str:
|
|
648
|
+
return f"{node.kind} {node.label}" if node.label else node.kind
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@_sql.register(n.CallStmt)
|
|
652
|
+
def _(node: n.CallStmt) -> str:
|
|
653
|
+
return f"CALL {_path(node.name)}({', '.join(_sql(a) for a in node.args)})"
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@_sql.register(n.ReturnStmt)
|
|
657
|
+
def _(node: n.ReturnStmt) -> str:
|
|
658
|
+
return "RETURN"
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
@_sql.register(n.RaiseStmt)
|
|
662
|
+
def _(node: n.RaiseStmt) -> str:
|
|
663
|
+
if node.message is not None:
|
|
664
|
+
return f"RAISE USING MESSAGE = {_sql(node.message)}"
|
|
665
|
+
return "RAISE"
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@_sql.register(n.ExecuteImmediate)
|
|
669
|
+
def _(node: n.ExecuteImmediate) -> str:
|
|
670
|
+
out = f"EXECUTE IMMEDIATE {_sql(node.sql_expr)}"
|
|
671
|
+
if node.into:
|
|
672
|
+
out += " INTO " + ", ".join(_ident(v) for v in node.into)
|
|
673
|
+
if node.using:
|
|
674
|
+
args = ", ".join(
|
|
675
|
+
_sql(e) + (f" AS {_ident(a)}" if a else "") for e, a in node.using
|
|
676
|
+
)
|
|
677
|
+
out += f" USING {args}"
|
|
678
|
+
return out
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
@_sql.register(n.AssertStmt)
|
|
682
|
+
def _(node: n.AssertStmt) -> str:
|
|
683
|
+
out = f"ASSERT {_sql(node.condition)}"
|
|
684
|
+
if node.message is not None:
|
|
685
|
+
out += f" AS {_sql(node.message)}"
|
|
686
|
+
return out
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@_sql.register(n.TransactionStmt)
|
|
690
|
+
def _(node: n.TransactionStmt) -> str:
|
|
691
|
+
return f"{node.kind} TRANSACTION"
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
@_sql.register(n.TruncateStmt)
|
|
695
|
+
def _(node: n.TruncateStmt) -> str:
|
|
696
|
+
return f"TRUNCATE TABLE {_path(node.table)}"
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
@_sql.register(n.DropStmt)
|
|
700
|
+
def _(node: n.DropStmt) -> str:
|
|
701
|
+
exists = "IF EXISTS " if node.if_exists else ""
|
|
702
|
+
return f"DROP {node.kind} {exists}{_path(node.name)}"
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
@_sql.register(n.RoutineParam)
|
|
706
|
+
def _(node: n.RoutineParam) -> str:
|
|
707
|
+
out = f"{node.mode} " if node.mode else ""
|
|
708
|
+
out += _ident(node.name)
|
|
709
|
+
if node.type is not None:
|
|
710
|
+
out += f" {_sql(node.type)}"
|
|
711
|
+
return out
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@_sql.register(n.CreateProcedure)
|
|
715
|
+
def _(node: n.CreateProcedure) -> str:
|
|
716
|
+
parts = ["CREATE"]
|
|
717
|
+
if node.replace:
|
|
718
|
+
parts.append("OR REPLACE")
|
|
719
|
+
parts.append("PROCEDURE")
|
|
720
|
+
if node.if_not_exists:
|
|
721
|
+
parts.append("IF NOT EXISTS")
|
|
722
|
+
parts.append(f"{_path(node.name)}({', '.join(_sql(p) for p in node.params)})")
|
|
723
|
+
if node.options_sql:
|
|
724
|
+
parts.append(f"OPTIONS{node.options_sql}")
|
|
725
|
+
parts.append(_sql(node.body))
|
|
726
|
+
return " ".join(parts)
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
@_sql.register(n.CreateFunction)
|
|
730
|
+
def _(node: n.CreateFunction) -> str:
|
|
731
|
+
parts = ["CREATE"]
|
|
732
|
+
if node.replace:
|
|
733
|
+
parts.append("OR REPLACE")
|
|
734
|
+
if node.temp:
|
|
735
|
+
parts.append("TEMP")
|
|
736
|
+
if node.aggregate:
|
|
737
|
+
parts.append("AGGREGATE")
|
|
738
|
+
if node.table_function:
|
|
739
|
+
parts.append("TABLE")
|
|
740
|
+
parts.append("FUNCTION")
|
|
741
|
+
if node.if_not_exists:
|
|
742
|
+
parts.append("IF NOT EXISTS")
|
|
743
|
+
parts.append(f"{_path(node.name)}({', '.join(_sql(p) for p in node.params)})")
|
|
744
|
+
if node.returns_table:
|
|
745
|
+
inner = ", ".join(
|
|
746
|
+
(f"{_ident(f.name)} " if f.name else "") + _sql(f.type)
|
|
747
|
+
for f in node.returns_table
|
|
748
|
+
)
|
|
749
|
+
parts.append(f"RETURNS TABLE<{inner}>")
|
|
750
|
+
elif node.returns is not None:
|
|
751
|
+
parts.append(f"RETURNS {_sql(node.returns)}")
|
|
752
|
+
if node.deterministic is True:
|
|
753
|
+
parts.append("DETERMINISTIC")
|
|
754
|
+
elif node.deterministic is False:
|
|
755
|
+
parts.append("NOT DETERMINISTIC")
|
|
756
|
+
if node.language:
|
|
757
|
+
parts.append(f"LANGUAGE {node.language}")
|
|
758
|
+
if node.options_sql:
|
|
759
|
+
parts.append(f"OPTIONS{node.options_sql}")
|
|
760
|
+
if node.body_query is not None:
|
|
761
|
+
parts.append(f"AS ({_sql(node.body_query)})")
|
|
762
|
+
elif node.body_expr is not None:
|
|
763
|
+
parts.append(f"AS ({_sql(node.body_expr)})")
|
|
764
|
+
elif node.body_string is not None:
|
|
765
|
+
parts.append(f"AS {_str_lit(node.body_string)}")
|
|
766
|
+
return " ".join(parts)
|