sqlglot 27.27.0__py3-none-any.whl → 28.4.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.
- sqlglot/__init__.py +1 -0
- sqlglot/__main__.py +6 -4
- sqlglot/_version.py +2 -2
- sqlglot/dialects/bigquery.py +118 -279
- sqlglot/dialects/clickhouse.py +73 -5
- sqlglot/dialects/databricks.py +38 -1
- sqlglot/dialects/dialect.py +354 -275
- sqlglot/dialects/dremio.py +4 -1
- sqlglot/dialects/duckdb.py +754 -25
- sqlglot/dialects/exasol.py +243 -10
- sqlglot/dialects/hive.py +8 -8
- sqlglot/dialects/mysql.py +14 -4
- sqlglot/dialects/oracle.py +29 -0
- sqlglot/dialects/postgres.py +60 -26
- sqlglot/dialects/presto.py +47 -16
- sqlglot/dialects/redshift.py +16 -0
- sqlglot/dialects/risingwave.py +3 -0
- sqlglot/dialects/singlestore.py +12 -3
- sqlglot/dialects/snowflake.py +239 -218
- sqlglot/dialects/spark.py +15 -4
- sqlglot/dialects/spark2.py +11 -48
- sqlglot/dialects/sqlite.py +10 -0
- sqlglot/dialects/starrocks.py +3 -0
- sqlglot/dialects/teradata.py +5 -8
- sqlglot/dialects/trino.py +6 -0
- sqlglot/dialects/tsql.py +61 -22
- sqlglot/diff.py +4 -2
- sqlglot/errors.py +69 -0
- sqlglot/executor/__init__.py +5 -10
- sqlglot/executor/python.py +1 -29
- sqlglot/expressions.py +637 -100
- sqlglot/generator.py +160 -43
- sqlglot/helper.py +2 -44
- sqlglot/lineage.py +10 -4
- sqlglot/optimizer/annotate_types.py +247 -140
- sqlglot/optimizer/canonicalize.py +6 -1
- sqlglot/optimizer/eliminate_joins.py +1 -1
- sqlglot/optimizer/eliminate_subqueries.py +2 -2
- sqlglot/optimizer/merge_subqueries.py +5 -5
- sqlglot/optimizer/normalize.py +20 -13
- sqlglot/optimizer/normalize_identifiers.py +17 -3
- sqlglot/optimizer/optimizer.py +4 -0
- sqlglot/optimizer/pushdown_predicates.py +1 -1
- sqlglot/optimizer/qualify.py +18 -10
- sqlglot/optimizer/qualify_columns.py +122 -275
- sqlglot/optimizer/qualify_tables.py +128 -76
- sqlglot/optimizer/resolver.py +374 -0
- sqlglot/optimizer/scope.py +27 -16
- sqlglot/optimizer/simplify.py +1075 -959
- sqlglot/optimizer/unnest_subqueries.py +12 -2
- sqlglot/parser.py +296 -170
- sqlglot/planner.py +2 -2
- sqlglot/schema.py +15 -4
- sqlglot/tokens.py +42 -7
- sqlglot/transforms.py +77 -22
- sqlglot/typing/__init__.py +316 -0
- sqlglot/typing/bigquery.py +376 -0
- sqlglot/typing/hive.py +12 -0
- sqlglot/typing/presto.py +24 -0
- sqlglot/typing/snowflake.py +505 -0
- sqlglot/typing/spark2.py +58 -0
- sqlglot/typing/tsql.py +9 -0
- {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/METADATA +2 -2
- sqlglot-28.4.0.dist-info/RECORD +92 -0
- sqlglot-27.27.0.dist-info/RECORD +0 -84
- {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/WHEEL +0 -0
- {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/licenses/LICENSE +0 -0
- {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/top_level.txt +0 -0
sqlglot/generator.py
CHANGED
|
@@ -80,7 +80,7 @@ class Generator(metaclass=_Generator):
|
|
|
80
80
|
Default: False.
|
|
81
81
|
identify: Determines when an identifier should be quoted. Possible values are:
|
|
82
82
|
False (default): Never quote, except in cases where it's mandatory by the dialect.
|
|
83
|
-
True
|
|
83
|
+
True: Always quote except for specials cases.
|
|
84
84
|
'safe': Only quote identifiers that are case insensitive.
|
|
85
85
|
normalize: Whether to normalize identifiers to lowercase.
|
|
86
86
|
Default: False.
|
|
@@ -137,6 +137,8 @@ class Generator(metaclass=_Generator):
|
|
|
137
137
|
exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
|
|
138
138
|
exp.CredentialsProperty: lambda self,
|
|
139
139
|
e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})",
|
|
140
|
+
exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
|
|
141
|
+
exp.SessionUser: lambda *_: "SESSION_USER",
|
|
140
142
|
exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
|
|
141
143
|
exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
|
|
142
144
|
exp.DynamicProperty: lambda *_: "DYNAMIC",
|
|
@@ -177,6 +179,8 @@ class Generator(metaclass=_Generator):
|
|
|
177
179
|
exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
|
|
178
180
|
exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary`
|
|
179
181
|
exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
|
|
182
|
+
exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
|
|
183
|
+
exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
|
|
180
184
|
exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
|
|
181
185
|
exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
|
|
182
186
|
exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
|
|
@@ -184,6 +188,7 @@ class Generator(metaclass=_Generator):
|
|
|
184
188
|
exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
|
|
185
189
|
exp.ProjectionPolicyColumnConstraint: lambda self,
|
|
186
190
|
e: f"PROJECTION POLICY {self.sql(e, 'this')}",
|
|
191
|
+
exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
|
|
187
192
|
exp.Put: lambda self, e: self.get_put_sql(e),
|
|
188
193
|
exp.RemoteWithConnectionModelProperty: lambda self,
|
|
189
194
|
e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
|
|
@@ -198,8 +203,7 @@ class Generator(metaclass=_Generator):
|
|
|
198
203
|
exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
|
|
199
204
|
exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
|
|
200
205
|
exp.SqlReadWriteProperty: lambda _, e: e.name,
|
|
201
|
-
exp.SqlSecurityProperty: lambda
|
|
202
|
-
e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
|
|
206
|
+
exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
|
|
203
207
|
exp.StabilityProperty: lambda _, e: e.name,
|
|
204
208
|
exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
|
|
205
209
|
exp.StreamingTableProperty: lambda *_: "STREAMING",
|
|
@@ -217,7 +221,6 @@ class Generator(metaclass=_Generator):
|
|
|
217
221
|
exp.UnloggedProperty: lambda *_: "UNLOGGED",
|
|
218
222
|
exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
|
|
219
223
|
exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
|
|
220
|
-
exp.Uuid: lambda *_: "UUID()",
|
|
221
224
|
exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
|
|
222
225
|
exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
|
|
223
226
|
exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
|
|
@@ -227,7 +230,6 @@ class Generator(metaclass=_Generator):
|
|
|
227
230
|
exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
|
|
228
231
|
exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
|
|
229
232
|
exp.VolatileProperty: lambda *_: "VOLATILE",
|
|
230
|
-
exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})",
|
|
231
233
|
exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
|
|
232
234
|
exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
|
|
233
235
|
exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
|
|
@@ -509,6 +511,9 @@ class Generator(metaclass=_Generator):
|
|
|
509
511
|
# Prefix which is appended to exp.Table expressions in MATCH AGAINST
|
|
510
512
|
MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
|
|
511
513
|
|
|
514
|
+
# Whether to include the VARIABLE keyword for SET assignments
|
|
515
|
+
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
|
|
516
|
+
|
|
512
517
|
TYPE_MAPPING = {
|
|
513
518
|
exp.DataType.Type.DATETIME2: "TIMESTAMP",
|
|
514
519
|
exp.DataType.Type.NCHAR: "CHAR",
|
|
@@ -1028,6 +1033,9 @@ class Generator(metaclass=_Generator):
|
|
|
1028
1033
|
|
|
1029
1034
|
return f"{self.column_parts(expression)}{join_mark}"
|
|
1030
1035
|
|
|
1036
|
+
def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
|
|
1037
|
+
return self.column_sql(expression)
|
|
1038
|
+
|
|
1031
1039
|
def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
|
|
1032
1040
|
this = self.sql(expression, "this")
|
|
1033
1041
|
this = f" {this}" if this else ""
|
|
@@ -1302,7 +1310,7 @@ class Generator(metaclass=_Generator):
|
|
|
1302
1310
|
return f"${tag}${self.sql(expression, 'this')}${tag}$"
|
|
1303
1311
|
|
|
1304
1312
|
def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
|
|
1305
|
-
with_ = self.sql(expression, "
|
|
1313
|
+
with_ = self.sql(expression, "with_")
|
|
1306
1314
|
if with_:
|
|
1307
1315
|
sql = f"{with_}{self.sep()}{sql}"
|
|
1308
1316
|
return sql
|
|
@@ -1391,7 +1399,20 @@ class Generator(metaclass=_Generator):
|
|
|
1391
1399
|
delimiter=self.dialect.BYTE_END,
|
|
1392
1400
|
escaped_delimiter=self._escaped_byte_quote_end,
|
|
1393
1401
|
)
|
|
1394
|
-
|
|
1402
|
+
is_bytes = expression.args.get("is_bytes", False)
|
|
1403
|
+
delimited_byte_string = (
|
|
1404
|
+
f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
|
|
1405
|
+
)
|
|
1406
|
+
if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
|
|
1407
|
+
return self.sql(
|
|
1408
|
+
exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect)
|
|
1409
|
+
)
|
|
1410
|
+
if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
|
|
1411
|
+
return self.sql(
|
|
1412
|
+
exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect)
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1415
|
+
return delimited_byte_string
|
|
1395
1416
|
return this
|
|
1396
1417
|
|
|
1397
1418
|
def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
|
|
@@ -1487,13 +1508,14 @@ class Generator(metaclass=_Generator):
|
|
|
1487
1508
|
cluster = f" {cluster}" if cluster else ""
|
|
1488
1509
|
where = self.sql(expression, "where")
|
|
1489
1510
|
returning = self.sql(expression, "returning")
|
|
1511
|
+
order = self.sql(expression, "order")
|
|
1490
1512
|
limit = self.sql(expression, "limit")
|
|
1491
1513
|
tables = self.expressions(expression, key="tables")
|
|
1492
1514
|
tables = f" {tables}" if tables else ""
|
|
1493
1515
|
if self.RETURNING_END:
|
|
1494
|
-
expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
|
|
1516
|
+
expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
|
|
1495
1517
|
else:
|
|
1496
|
-
expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
|
|
1518
|
+
expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
|
|
1497
1519
|
return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
|
|
1498
1520
|
|
|
1499
1521
|
def drop_sql(self, expression: exp.Drop) -> str:
|
|
@@ -1660,7 +1682,7 @@ class Generator(metaclass=_Generator):
|
|
|
1660
1682
|
text = text.replace(self._identifier_end, self._escaped_identifier_end)
|
|
1661
1683
|
if (
|
|
1662
1684
|
expression.quoted
|
|
1663
|
-
or self.dialect.
|
|
1685
|
+
or self.dialect.can_quote(expression, self.identify)
|
|
1664
1686
|
or lower in self.RESERVED_KEYWORDS
|
|
1665
1687
|
or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
|
|
1666
1688
|
):
|
|
@@ -1927,7 +1949,7 @@ class Generator(metaclass=_Generator):
|
|
|
1927
1949
|
|
|
1928
1950
|
sql = f"SYSTEM_VERSIONING={on_sql}"
|
|
1929
1951
|
|
|
1930
|
-
return f"WITH({sql})" if expression.args.get("
|
|
1952
|
+
return f"WITH({sql})" if expression.args.get("with_") else sql
|
|
1931
1953
|
|
|
1932
1954
|
def insert_sql(self, expression: exp.Insert) -> str:
|
|
1933
1955
|
hint = self.sql(expression, "hint")
|
|
@@ -2100,7 +2122,13 @@ class Generator(metaclass=_Generator):
|
|
|
2100
2122
|
if rows_from:
|
|
2101
2123
|
table = f"ROWS FROM {self.wrap(rows_from)}"
|
|
2102
2124
|
|
|
2103
|
-
|
|
2125
|
+
indexed = expression.args.get("indexed")
|
|
2126
|
+
if indexed is not None:
|
|
2127
|
+
indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
|
|
2128
|
+
else:
|
|
2129
|
+
indexed = ""
|
|
2130
|
+
|
|
2131
|
+
return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
|
|
2104
2132
|
|
|
2105
2133
|
def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
|
|
2106
2134
|
table = self.func("TABLE", expression.this)
|
|
@@ -2151,14 +2179,15 @@ class Generator(metaclass=_Generator):
|
|
|
2151
2179
|
if expression.this:
|
|
2152
2180
|
this = self.sql(expression, "this")
|
|
2153
2181
|
if not expressions:
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2182
|
+
sql = f"UNPIVOT {this}"
|
|
2183
|
+
else:
|
|
2184
|
+
on = f"{self.seg('ON')} {expressions}"
|
|
2185
|
+
into = self.sql(expression, "into")
|
|
2186
|
+
into = f"{self.seg('INTO')} {into}" if into else ""
|
|
2187
|
+
using = self.expressions(expression, key="using", flat=True)
|
|
2188
|
+
using = f"{self.seg('USING')} {using}" if using else ""
|
|
2189
|
+
sql = f"{direction} {this}{on}{into}{using}{group}"
|
|
2190
|
+
return self.prepend_ctes(expression, sql)
|
|
2162
2191
|
|
|
2163
2192
|
alias = self.sql(expression, "alias")
|
|
2164
2193
|
alias = f" AS {alias}" if alias else ""
|
|
@@ -2181,7 +2210,8 @@ class Generator(metaclass=_Generator):
|
|
|
2181
2210
|
|
|
2182
2211
|
default_on_null = self.sql(expression, "default_on_null")
|
|
2183
2212
|
default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
|
|
2184
|
-
|
|
2213
|
+
sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
|
|
2214
|
+
return self.prepend_ctes(expression, sql)
|
|
2185
2215
|
|
|
2186
2216
|
def version_sql(self, expression: exp.Version) -> str:
|
|
2187
2217
|
this = f"FOR {expression.name}"
|
|
@@ -2195,7 +2225,7 @@ class Generator(metaclass=_Generator):
|
|
|
2195
2225
|
def update_sql(self, expression: exp.Update) -> str:
|
|
2196
2226
|
this = self.sql(expression, "this")
|
|
2197
2227
|
set_sql = self.expressions(expression, flat=True)
|
|
2198
|
-
from_sql = self.sql(expression, "
|
|
2228
|
+
from_sql = self.sql(expression, "from_")
|
|
2199
2229
|
where_sql = self.sql(expression, "where")
|
|
2200
2230
|
returning = self.sql(expression, "returning")
|
|
2201
2231
|
order = self.sql(expression, "order")
|
|
@@ -2223,6 +2253,7 @@ class Generator(metaclass=_Generator):
|
|
|
2223
2253
|
and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
|
|
2224
2254
|
else values
|
|
2225
2255
|
)
|
|
2256
|
+
values = self.query_modifiers(expression, values)
|
|
2226
2257
|
return f"{values} AS {alias}" if alias else values
|
|
2227
2258
|
|
|
2228
2259
|
# Converts `VALUES...` expression into a series of select unions.
|
|
@@ -2333,7 +2364,7 @@ class Generator(metaclass=_Generator):
|
|
|
2333
2364
|
op
|
|
2334
2365
|
for op in (
|
|
2335
2366
|
expression.method,
|
|
2336
|
-
"GLOBAL" if expression.args.get("
|
|
2367
|
+
"GLOBAL" if expression.args.get("global_") else None,
|
|
2337
2368
|
side,
|
|
2338
2369
|
expression.kind,
|
|
2339
2370
|
expression.hint if self.JOIN_HINTS else None,
|
|
@@ -2440,12 +2471,15 @@ class Generator(metaclass=_Generator):
|
|
|
2440
2471
|
|
|
2441
2472
|
def setitem_sql(self, expression: exp.SetItem) -> str:
|
|
2442
2473
|
kind = self.sql(expression, "kind")
|
|
2443
|
-
|
|
2474
|
+
if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
|
|
2475
|
+
kind = ""
|
|
2476
|
+
else:
|
|
2477
|
+
kind = f"{kind} " if kind else ""
|
|
2444
2478
|
this = self.sql(expression, "this")
|
|
2445
2479
|
expressions = self.expressions(expression)
|
|
2446
2480
|
collate = self.sql(expression, "collate")
|
|
2447
2481
|
collate = f" COLLATE {collate}" if collate else ""
|
|
2448
|
-
global_ = "GLOBAL " if expression.args.get("
|
|
2482
|
+
global_ = "GLOBAL " if expression.args.get("global_") else ""
|
|
2449
2483
|
return f"{global_}{kind}{this}{expressions}{collate}"
|
|
2450
2484
|
|
|
2451
2485
|
def set_sql(self, expression: exp.Set) -> str:
|
|
@@ -2530,6 +2564,12 @@ class Generator(metaclass=_Generator):
|
|
|
2530
2564
|
def boolean_sql(self, expression: exp.Boolean) -> str:
|
|
2531
2565
|
return "TRUE" if expression.this else "FALSE"
|
|
2532
2566
|
|
|
2567
|
+
def booland_sql(self, expression: exp.Booland) -> str:
|
|
2568
|
+
return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
|
|
2569
|
+
|
|
2570
|
+
def boolor_sql(self, expression: exp.Boolor) -> str:
|
|
2571
|
+
return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
|
|
2572
|
+
|
|
2533
2573
|
def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
|
|
2534
2574
|
this = self.sql(expression, "this")
|
|
2535
2575
|
this = f"{this} " if this else this
|
|
@@ -2537,7 +2577,7 @@ class Generator(metaclass=_Generator):
|
|
|
2537
2577
|
return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
|
|
2538
2578
|
|
|
2539
2579
|
def withfill_sql(self, expression: exp.WithFill) -> str:
|
|
2540
|
-
from_sql = self.sql(expression, "
|
|
2580
|
+
from_sql = self.sql(expression, "from_")
|
|
2541
2581
|
from_sql = f" FROM {from_sql}" if from_sql else ""
|
|
2542
2582
|
to_sql = self.sql(expression, "to")
|
|
2543
2583
|
to_sql = f" TO {to_sql}" if to_sql else ""
|
|
@@ -2698,7 +2738,7 @@ class Generator(metaclass=_Generator):
|
|
|
2698
2738
|
return f" {options}" if options else ""
|
|
2699
2739
|
|
|
2700
2740
|
def for_modifiers(self, expression: exp.Expression) -> str:
|
|
2701
|
-
for_modifiers = self.expressions(expression, key="
|
|
2741
|
+
for_modifiers = self.expressions(expression, key="for_")
|
|
2702
2742
|
return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
|
|
2703
2743
|
|
|
2704
2744
|
def queryoption_sql(self, expression: exp.QueryOption) -> str:
|
|
@@ -2769,11 +2809,11 @@ class Generator(metaclass=_Generator):
|
|
|
2769
2809
|
expression,
|
|
2770
2810
|
f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
|
|
2771
2811
|
self.sql(expression, "into", comment=False),
|
|
2772
|
-
self.sql(expression, "
|
|
2812
|
+
self.sql(expression, "from_", comment=False),
|
|
2773
2813
|
)
|
|
2774
2814
|
|
|
2775
2815
|
# If both the CTE and SELECT clauses have comments, generate the latter earlier
|
|
2776
|
-
if expression.args.get("
|
|
2816
|
+
if expression.args.get("with_"):
|
|
2777
2817
|
sql = self.maybe_comment(sql, expression)
|
|
2778
2818
|
expression.pop_comments()
|
|
2779
2819
|
|
|
@@ -2801,7 +2841,7 @@ class Generator(metaclass=_Generator):
|
|
|
2801
2841
|
return ""
|
|
2802
2842
|
|
|
2803
2843
|
def star_sql(self, expression: exp.Star) -> str:
|
|
2804
|
-
except_ = self.expressions(expression, key="
|
|
2844
|
+
except_ = self.expressions(expression, key="except_", flat=True)
|
|
2805
2845
|
except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
|
|
2806
2846
|
replace = self.expressions(expression, key="replace", flat=True)
|
|
2807
2847
|
replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
|
|
@@ -3061,6 +3101,14 @@ class Generator(metaclass=_Generator):
|
|
|
3061
3101
|
return args
|
|
3062
3102
|
|
|
3063
3103
|
def concat_sql(self, expression: exp.Concat) -> str:
|
|
3104
|
+
if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
|
|
3105
|
+
# Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
|
|
3106
|
+
# Transpile to double pipe operators, which typically returns NULL if any args are NULL
|
|
3107
|
+
# instead of coalescing them to empty string.
|
|
3108
|
+
from sqlglot.dialects.dialect import concat_to_dpipe_sql
|
|
3109
|
+
|
|
3110
|
+
return concat_to_dpipe_sql(self, expression)
|
|
3111
|
+
|
|
3064
3112
|
expressions = self.convert_concat_args(expression)
|
|
3065
3113
|
|
|
3066
3114
|
# Some dialects don't allow a single-argument CONCAT call
|
|
@@ -3092,11 +3140,13 @@ class Generator(metaclass=_Generator):
|
|
|
3092
3140
|
return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
|
|
3093
3141
|
|
|
3094
3142
|
def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
|
|
3143
|
+
this = self.sql(expression, "this")
|
|
3144
|
+
this = f" {this}" if this else ""
|
|
3095
3145
|
expressions = self.expressions(expression, flat=True)
|
|
3096
3146
|
include = self.sql(expression, "include")
|
|
3097
3147
|
options = self.expressions(expression, key="options", flat=True, sep=" ")
|
|
3098
3148
|
options = f" {options}" if options else ""
|
|
3099
|
-
return f"PRIMARY KEY ({expressions}){include}{options}"
|
|
3149
|
+
return f"PRIMARY KEY{this} ({expressions}){include}{options}"
|
|
3100
3150
|
|
|
3101
3151
|
def if_sql(self, expression: exp.If) -> str:
|
|
3102
3152
|
return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
|
|
@@ -3794,6 +3844,10 @@ class Generator(metaclass=_Generator):
|
|
|
3794
3844
|
for expr in exprs[1:]:
|
|
3795
3845
|
like_expr = connective(like_expr, exp_class(this=this, expression=expr))
|
|
3796
3846
|
|
|
3847
|
+
parent = expression.parent
|
|
3848
|
+
if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition):
|
|
3849
|
+
like_expr = exp.paren(like_expr, copy=False)
|
|
3850
|
+
|
|
3797
3851
|
return self.sql(like_expr)
|
|
3798
3852
|
|
|
3799
3853
|
return self.binary(expression, op)
|
|
@@ -3804,6 +3858,9 @@ class Generator(metaclass=_Generator):
|
|
|
3804
3858
|
def ilike_sql(self, expression: exp.ILike) -> str:
|
|
3805
3859
|
return self._like_sql(expression)
|
|
3806
3860
|
|
|
3861
|
+
def match_sql(self, expression: exp.Match) -> str:
|
|
3862
|
+
return self.binary(expression, "MATCH")
|
|
3863
|
+
|
|
3807
3864
|
def similarto_sql(self, expression: exp.SimilarTo) -> str:
|
|
3808
3865
|
return self.binary(expression, "SIMILAR TO")
|
|
3809
3866
|
|
|
@@ -3828,9 +3885,6 @@ class Generator(metaclass=_Generator):
|
|
|
3828
3885
|
def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
|
|
3829
3886
|
return self.binary(expression, "IS DISTINCT FROM")
|
|
3830
3887
|
|
|
3831
|
-
def slice_sql(self, expression: exp.Slice) -> str:
|
|
3832
|
-
return self.binary(expression, ":")
|
|
3833
|
-
|
|
3834
3888
|
def sub_sql(self, expression: exp.Sub) -> str:
|
|
3835
3889
|
return self.binary(expression, "-")
|
|
3836
3890
|
|
|
@@ -4058,7 +4112,9 @@ class Generator(metaclass=_Generator):
|
|
|
4058
4112
|
if isinstance(then_expression.args.get("expressions"), exp.Star):
|
|
4059
4113
|
then = f"UPDATE {self.sql(then_expression, 'expressions')}"
|
|
4060
4114
|
else:
|
|
4061
|
-
|
|
4115
|
+
expressions_sql = self.expressions(then_expression)
|
|
4116
|
+
then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
|
|
4117
|
+
|
|
4062
4118
|
else:
|
|
4063
4119
|
then = self.sql(then_expression)
|
|
4064
4120
|
return f"WHEN {matched}{source}{condition} THEN {then}"
|
|
@@ -4077,9 +4133,15 @@ class Generator(metaclass=_Generator):
|
|
|
4077
4133
|
|
|
4078
4134
|
this = self.sql(table)
|
|
4079
4135
|
using = f"USING {self.sql(expression, 'using')}"
|
|
4080
|
-
on = f"ON {self.sql(expression, 'on')}"
|
|
4081
4136
|
whens = self.sql(expression, "whens")
|
|
4082
4137
|
|
|
4138
|
+
on = self.sql(expression, "on")
|
|
4139
|
+
on = f"ON {on}" if on else ""
|
|
4140
|
+
|
|
4141
|
+
if not on:
|
|
4142
|
+
on = self.expressions(expression, key="using_cond")
|
|
4143
|
+
on = f"USING ({on})" if on else ""
|
|
4144
|
+
|
|
4083
4145
|
returning = self.sql(expression, "returning")
|
|
4084
4146
|
if returning:
|
|
4085
4147
|
whens = f"{whens}{returning}"
|
|
@@ -4243,10 +4305,12 @@ class Generator(metaclass=_Generator):
|
|
|
4243
4305
|
def comprehension_sql(self, expression: exp.Comprehension) -> str:
|
|
4244
4306
|
this = self.sql(expression, "this")
|
|
4245
4307
|
expr = self.sql(expression, "expression")
|
|
4308
|
+
position = self.sql(expression, "position")
|
|
4309
|
+
position = f", {position}" if position else ""
|
|
4246
4310
|
iterator = self.sql(expression, "iterator")
|
|
4247
4311
|
condition = self.sql(expression, "condition")
|
|
4248
4312
|
condition = f" IF {condition}" if condition else ""
|
|
4249
|
-
return f"{this} FOR {expr} IN {iterator}{condition}"
|
|
4313
|
+
return f"{this} FOR {expr}{position} IN {iterator}{condition}"
|
|
4250
4314
|
|
|
4251
4315
|
def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
|
|
4252
4316
|
return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
|
|
@@ -4321,8 +4385,8 @@ class Generator(metaclass=_Generator):
|
|
|
4321
4385
|
|
|
4322
4386
|
def refresh_sql(self, expression: exp.Refresh) -> str:
|
|
4323
4387
|
this = self.sql(expression, "this")
|
|
4324
|
-
|
|
4325
|
-
return f"REFRESH {
|
|
4388
|
+
kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
|
|
4389
|
+
return f"REFRESH {kind}{this}"
|
|
4326
4390
|
|
|
4327
4391
|
def toarray_sql(self, expression: exp.ToArray) -> str:
|
|
4328
4392
|
arg = expression.this
|
|
@@ -4780,7 +4844,7 @@ class Generator(metaclass=_Generator):
|
|
|
4780
4844
|
this = self.sql(expression, "this")
|
|
4781
4845
|
this = f" {this}" if this else ""
|
|
4782
4846
|
|
|
4783
|
-
_with = expression.args.get("
|
|
4847
|
+
_with = expression.args.get("with_")
|
|
4784
4848
|
|
|
4785
4849
|
if _with is None:
|
|
4786
4850
|
with_sql = ""
|
|
@@ -4890,6 +4954,14 @@ class Generator(metaclass=_Generator):
|
|
|
4890
4954
|
|
|
4891
4955
|
return array_agg
|
|
4892
4956
|
|
|
4957
|
+
def slice_sql(self, expression: exp.Slice) -> str:
|
|
4958
|
+
step = self.sql(expression, "step")
|
|
4959
|
+
end = self.sql(expression.expression)
|
|
4960
|
+
begin = self.sql(expression.this)
|
|
4961
|
+
|
|
4962
|
+
sql = f"{end}:{step}" if step else end
|
|
4963
|
+
return f"{begin}:{sql}" if sql else f"{begin}:"
|
|
4964
|
+
|
|
4893
4965
|
def apply_sql(self, expression: exp.Apply) -> str:
|
|
4894
4966
|
this = self.sql(expression, "this")
|
|
4895
4967
|
expr = self.sql(expression, "expression")
|
|
@@ -4964,8 +5036,8 @@ class Generator(metaclass=_Generator):
|
|
|
4964
5036
|
def overlay_sql(self, expression: exp.Overlay):
|
|
4965
5037
|
this = self.sql(expression, "this")
|
|
4966
5038
|
expr = self.sql(expression, "expression")
|
|
4967
|
-
from_sql = self.sql(expression, "
|
|
4968
|
-
for_sql = self.sql(expression, "
|
|
5039
|
+
from_sql = self.sql(expression, "from_")
|
|
5040
|
+
for_sql = self.sql(expression, "for_")
|
|
4969
5041
|
for_sql = f" FOR {for_sql}" if for_sql else ""
|
|
4970
5042
|
|
|
4971
5043
|
return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
|
|
@@ -5296,9 +5368,11 @@ class Generator(metaclass=_Generator):
|
|
|
5296
5368
|
expression, "metrics", dynamic=True, skip_first=True, skip_last=True
|
|
5297
5369
|
)
|
|
5298
5370
|
metrics = self.seg(f"METRICS {metrics}") if metrics else ""
|
|
5371
|
+
facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
|
|
5372
|
+
facts = self.seg(f"FACTS {facts}") if facts else ""
|
|
5299
5373
|
where = self.sql(expression, "where")
|
|
5300
5374
|
where = self.seg(f"WHERE {where}") if where else ""
|
|
5301
|
-
body = self.indent(this + metrics + dimensions + where, skip_first=True)
|
|
5375
|
+
body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
|
|
5302
5376
|
return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
|
|
5303
5377
|
|
|
5304
5378
|
def getextract_sql(self, expression: exp.GetExtract) -> str:
|
|
@@ -5350,3 +5424,46 @@ class Generator(metaclass=_Generator):
|
|
|
5350
5424
|
|
|
5351
5425
|
def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
|
|
5352
5426
|
return self.func("DIRECTORY", expression.this)
|
|
5427
|
+
|
|
5428
|
+
def uuid_sql(self, expression: exp.Uuid) -> str:
|
|
5429
|
+
is_string = expression.args.get("is_string", False)
|
|
5430
|
+
uuid_func_sql = self.func("UUID")
|
|
5431
|
+
|
|
5432
|
+
if is_string and not self.dialect.UUID_IS_STRING_TYPE:
|
|
5433
|
+
return self.sql(
|
|
5434
|
+
exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect)
|
|
5435
|
+
)
|
|
5436
|
+
|
|
5437
|
+
return uuid_func_sql
|
|
5438
|
+
|
|
5439
|
+
def initcap_sql(self, expression: exp.Initcap) -> str:
|
|
5440
|
+
delimiters = expression.expression
|
|
5441
|
+
|
|
5442
|
+
if delimiters:
|
|
5443
|
+
# do not generate delimiters arg if we are round-tripping from default delimiters
|
|
5444
|
+
if (
|
|
5445
|
+
delimiters.is_string
|
|
5446
|
+
and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
|
|
5447
|
+
):
|
|
5448
|
+
delimiters = None
|
|
5449
|
+
elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
|
|
5450
|
+
self.unsupported("INITCAP does not support custom delimiters")
|
|
5451
|
+
delimiters = None
|
|
5452
|
+
|
|
5453
|
+
return self.func("INITCAP", expression.this, delimiters)
|
|
5454
|
+
|
|
5455
|
+
def localtime_sql(self, expression: exp.Localtime) -> str:
|
|
5456
|
+
this = expression.this
|
|
5457
|
+
return self.func("LOCALTIME", this) if this else "LOCALTIME"
|
|
5458
|
+
|
|
5459
|
+
def localtimestamp_sql(self, expression: exp.Localtime) -> str:
|
|
5460
|
+
this = expression.this
|
|
5461
|
+
return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
|
|
5462
|
+
|
|
5463
|
+
def weekstart_sql(self, expression: exp.WeekStart) -> str:
|
|
5464
|
+
this = expression.this.name.upper()
|
|
5465
|
+
if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
|
|
5466
|
+
# BigQuery specific optimization since WEEK(SUNDAY) == WEEK
|
|
5467
|
+
return "WEEK"
|
|
5468
|
+
|
|
5469
|
+
return self.func("WEEK", expression.this)
|
sqlglot/helper.py
CHANGED
|
@@ -7,7 +7,6 @@ import re
|
|
|
7
7
|
import sys
|
|
8
8
|
import typing as t
|
|
9
9
|
from collections.abc import Collection, Set
|
|
10
|
-
from contextlib import contextmanager
|
|
11
10
|
from copy import copy
|
|
12
11
|
from difflib import get_close_matches
|
|
13
12
|
from enum import Enum
|
|
@@ -140,7 +139,7 @@ def csv(*args: str, sep: str = ", ") -> str:
|
|
|
140
139
|
def subclasses(
|
|
141
140
|
module_name: str,
|
|
142
141
|
classes: t.Type | t.Tuple[t.Type, ...],
|
|
143
|
-
exclude: t.
|
|
142
|
+
exclude: t.Set[t.Type] = set(),
|
|
144
143
|
) -> t.List[t.Type]:
|
|
145
144
|
"""
|
|
146
145
|
Returns all subclasses for a collection of classes, possibly excluding some of them.
|
|
@@ -148,7 +147,7 @@ def subclasses(
|
|
|
148
147
|
Args:
|
|
149
148
|
module_name: The name of the module to search for subclasses in.
|
|
150
149
|
classes: Class(es) we want to find the subclasses of.
|
|
151
|
-
exclude:
|
|
150
|
+
exclude: Classes we want to exclude from the returned list.
|
|
152
151
|
|
|
153
152
|
Returns:
|
|
154
153
|
The target subclasses.
|
|
@@ -272,47 +271,6 @@ def tsort(dag: t.Dict[T, t.Set[T]]) -> t.List[T]:
|
|
|
272
271
|
return result
|
|
273
272
|
|
|
274
273
|
|
|
275
|
-
def open_file(file_name: str) -> t.TextIO:
|
|
276
|
-
"""Open a file that may be compressed as gzip and return it in universal newline mode."""
|
|
277
|
-
with open(file_name, "rb") as f:
|
|
278
|
-
gzipped = f.read(2) == b"\x1f\x8b"
|
|
279
|
-
|
|
280
|
-
if gzipped:
|
|
281
|
-
import gzip
|
|
282
|
-
|
|
283
|
-
return gzip.open(file_name, "rt", newline="")
|
|
284
|
-
|
|
285
|
-
return open(file_name, encoding="utf-8", newline="")
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@contextmanager
|
|
289
|
-
def csv_reader(read_csv: exp.ReadCSV) -> t.Any:
|
|
290
|
-
"""
|
|
291
|
-
Returns a csv reader given the expression `READ_CSV(name, ['delimiter', '|', ...])`.
|
|
292
|
-
|
|
293
|
-
Args:
|
|
294
|
-
read_csv: A `ReadCSV` function call.
|
|
295
|
-
|
|
296
|
-
Yields:
|
|
297
|
-
A python csv reader.
|
|
298
|
-
"""
|
|
299
|
-
args = read_csv.expressions
|
|
300
|
-
file = open_file(read_csv.name)
|
|
301
|
-
|
|
302
|
-
delimiter = ","
|
|
303
|
-
args = iter(arg.name for arg in args) # type: ignore
|
|
304
|
-
for k, v in zip(args, args):
|
|
305
|
-
if k == "delimiter":
|
|
306
|
-
delimiter = v
|
|
307
|
-
|
|
308
|
-
try:
|
|
309
|
-
import csv as csv_
|
|
310
|
-
|
|
311
|
-
yield csv_.reader(file, delimiter=delimiter)
|
|
312
|
-
finally:
|
|
313
|
-
file.close()
|
|
314
|
-
|
|
315
|
-
|
|
316
274
|
def find_new_name(taken: t.Collection[str], base: str) -> str:
|
|
317
275
|
"""
|
|
318
276
|
Searches for a new name.
|
sqlglot/lineage.py
CHANGED
|
@@ -73,6 +73,7 @@ def lineage(
|
|
|
73
73
|
dialect: DialectType = None,
|
|
74
74
|
scope: t.Optional[Scope] = None,
|
|
75
75
|
trim_selects: bool = True,
|
|
76
|
+
copy: bool = True,
|
|
76
77
|
**kwargs,
|
|
77
78
|
) -> Node:
|
|
78
79
|
"""Build the lineage graph for a column of a SQL query.
|
|
@@ -84,21 +85,26 @@ def lineage(
|
|
|
84
85
|
sources: A mapping of queries which will be used to continue building lineage.
|
|
85
86
|
dialect: The dialect of input SQL.
|
|
86
87
|
scope: A pre-created scope to use instead.
|
|
87
|
-
trim_selects: Whether
|
|
88
|
+
trim_selects: Whether to clean up selects by trimming to only relevant columns.
|
|
89
|
+
copy: Whether to copy the Expression arguments.
|
|
88
90
|
**kwargs: Qualification optimizer kwargs.
|
|
89
91
|
|
|
90
92
|
Returns:
|
|
91
93
|
A lineage node.
|
|
92
94
|
"""
|
|
93
95
|
|
|
94
|
-
expression = maybe_parse(sql, dialect=dialect)
|
|
96
|
+
expression = maybe_parse(sql, copy=copy, dialect=dialect)
|
|
95
97
|
column = normalize_identifiers.normalize_identifiers(column, dialect=dialect).name
|
|
96
98
|
|
|
97
99
|
if sources:
|
|
98
100
|
expression = exp.expand(
|
|
99
101
|
expression,
|
|
100
|
-
{
|
|
102
|
+
{
|
|
103
|
+
k: t.cast(exp.Query, maybe_parse(v, copy=copy, dialect=dialect))
|
|
104
|
+
for k, v in sources.items()
|
|
105
|
+
},
|
|
101
106
|
dialect=dialect,
|
|
107
|
+
copy=copy,
|
|
102
108
|
)
|
|
103
109
|
|
|
104
110
|
if not scope:
|
|
@@ -226,7 +232,7 @@ def to_node(
|
|
|
226
232
|
)
|
|
227
233
|
|
|
228
234
|
# if the select is a star add all scope sources as downstreams
|
|
229
|
-
if select.
|
|
235
|
+
if isinstance(select, exp.Star):
|
|
230
236
|
for source in scope.sources.values():
|
|
231
237
|
if isinstance(source, Scope):
|
|
232
238
|
source = source.expression
|