sqlglot 28.4.0__py3-none-any.whl → 28.8.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/_version.py +2 -2
- sqlglot/dialects/bigquery.py +20 -23
- sqlglot/dialects/clickhouse.py +2 -0
- sqlglot/dialects/dialect.py +355 -18
- sqlglot/dialects/doris.py +38 -90
- sqlglot/dialects/druid.py +1 -0
- sqlglot/dialects/duckdb.py +1739 -163
- sqlglot/dialects/exasol.py +17 -1
- sqlglot/dialects/hive.py +27 -2
- sqlglot/dialects/mysql.py +103 -11
- sqlglot/dialects/oracle.py +38 -1
- sqlglot/dialects/postgres.py +142 -33
- sqlglot/dialects/presto.py +6 -2
- sqlglot/dialects/redshift.py +7 -1
- sqlglot/dialects/singlestore.py +13 -3
- sqlglot/dialects/snowflake.py +271 -21
- sqlglot/dialects/spark.py +25 -0
- sqlglot/dialects/spark2.py +4 -3
- sqlglot/dialects/starrocks.py +152 -17
- sqlglot/dialects/trino.py +1 -0
- sqlglot/dialects/tsql.py +5 -0
- sqlglot/diff.py +1 -1
- sqlglot/expressions.py +239 -47
- sqlglot/generator.py +173 -44
- sqlglot/optimizer/annotate_types.py +129 -60
- sqlglot/optimizer/merge_subqueries.py +13 -2
- sqlglot/optimizer/qualify_columns.py +7 -0
- sqlglot/optimizer/resolver.py +19 -0
- sqlglot/optimizer/scope.py +12 -0
- sqlglot/optimizer/unnest_subqueries.py +7 -0
- sqlglot/parser.py +251 -58
- sqlglot/schema.py +186 -14
- sqlglot/tokens.py +36 -6
- sqlglot/transforms.py +6 -5
- sqlglot/typing/__init__.py +29 -10
- sqlglot/typing/bigquery.py +5 -10
- sqlglot/typing/duckdb.py +39 -0
- sqlglot/typing/hive.py +50 -1
- sqlglot/typing/mysql.py +32 -0
- sqlglot/typing/presto.py +0 -1
- sqlglot/typing/snowflake.py +80 -17
- sqlglot/typing/spark.py +29 -0
- sqlglot/typing/spark2.py +9 -1
- sqlglot/typing/tsql.py +21 -0
- {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/METADATA +47 -2
- sqlglot-28.8.0.dist-info/RECORD +95 -0
- {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/WHEEL +1 -1
- sqlglot-28.4.0.dist-info/RECORD +0 -92
- {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/licenses/LICENSE +0 -0
- {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/top_level.txt +0 -0
sqlglot/generator.py
CHANGED
|
@@ -112,6 +112,7 @@ class Generator(metaclass=_Generator):
|
|
|
112
112
|
|
|
113
113
|
TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = {
|
|
114
114
|
**JSON_PATH_PART_TRANSFORMS,
|
|
115
|
+
exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
|
|
115
116
|
exp.AllowedValuesProperty: lambda self,
|
|
116
117
|
e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
|
|
117
118
|
exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
|
|
@@ -169,6 +170,7 @@ class Generator(metaclass=_Generator):
|
|
|
169
170
|
exp.LocationProperty: lambda self, e: self.naked_property(e),
|
|
170
171
|
exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
|
|
171
172
|
exp.MaterializedProperty: lambda *_: "MATERIALIZED",
|
|
173
|
+
exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
|
|
172
174
|
exp.NonClusteredColumnConstraint: lambda self,
|
|
173
175
|
e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
|
|
174
176
|
exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
|
|
@@ -195,6 +197,7 @@ class Generator(metaclass=_Generator):
|
|
|
195
197
|
exp.ReturnsProperty: lambda self, e: (
|
|
196
198
|
"RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
|
|
197
199
|
),
|
|
200
|
+
exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
|
|
198
201
|
exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
|
|
199
202
|
exp.SecureProperty: lambda *_: "SECURE",
|
|
200
203
|
exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
|
|
@@ -227,6 +230,7 @@ class Generator(metaclass=_Generator):
|
|
|
227
230
|
exp.UtcTimestamp: lambda self, e: self.sql(
|
|
228
231
|
exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
|
|
229
232
|
),
|
|
233
|
+
exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
|
|
230
234
|
exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
|
|
231
235
|
exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
|
|
232
236
|
exp.VolatileProperty: lambda *_: "VOLATILE",
|
|
@@ -282,9 +286,15 @@ class Generator(metaclass=_Generator):
|
|
|
282
286
|
# The string used for creating an index on a table
|
|
283
287
|
INDEX_ON = "ON"
|
|
284
288
|
|
|
289
|
+
# Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
|
|
290
|
+
INOUT_SEPARATOR = " "
|
|
291
|
+
|
|
285
292
|
# Whether join hints should be generated
|
|
286
293
|
JOIN_HINTS = True
|
|
287
294
|
|
|
295
|
+
# Whether directed joins are supported
|
|
296
|
+
DIRECTED_JOINS = False
|
|
297
|
+
|
|
288
298
|
# Whether table hints should be generated
|
|
289
299
|
TABLE_HINTS = True
|
|
290
300
|
|
|
@@ -514,6 +524,11 @@ class Generator(metaclass=_Generator):
|
|
|
514
524
|
# Whether to include the VARIABLE keyword for SET assignments
|
|
515
525
|
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
|
|
516
526
|
|
|
527
|
+
# Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
|
|
528
|
+
# Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
|
|
529
|
+
# Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
|
|
530
|
+
UPDATE_STATEMENT_SUPPORTS_FROM = True
|
|
531
|
+
|
|
517
532
|
TYPE_MAPPING = {
|
|
518
533
|
exp.DataType.Type.DATETIME2: "TIMESTAMP",
|
|
519
534
|
exp.DataType.Type.NCHAR: "CHAR",
|
|
@@ -622,8 +637,10 @@ class Generator(metaclass=_Generator):
|
|
|
622
637
|
exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
|
|
623
638
|
exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
|
|
624
639
|
exp.Property: exp.Properties.Location.POST_WITH,
|
|
640
|
+
exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
|
|
625
641
|
exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
|
|
626
642
|
exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
|
|
643
|
+
exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
|
|
627
644
|
exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
|
|
628
645
|
exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
|
|
629
646
|
exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
|
|
@@ -1152,6 +1169,24 @@ class Generator(metaclass=_Generator):
|
|
|
1152
1169
|
options = f" {options}" if options else ""
|
|
1153
1170
|
return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
|
|
1154
1171
|
|
|
1172
|
+
def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
|
|
1173
|
+
input_ = expression.args.get("input_")
|
|
1174
|
+
output = expression.args.get("output")
|
|
1175
|
+
variadic = expression.args.get("variadic")
|
|
1176
|
+
|
|
1177
|
+
# VARIADIC is mutually exclusive with IN/OUT/INOUT
|
|
1178
|
+
if variadic:
|
|
1179
|
+
return "VARIADIC"
|
|
1180
|
+
|
|
1181
|
+
if input_ and output:
|
|
1182
|
+
return f"IN{self.INOUT_SEPARATOR}OUT"
|
|
1183
|
+
if input_:
|
|
1184
|
+
return "IN"
|
|
1185
|
+
if output:
|
|
1186
|
+
return "OUT"
|
|
1187
|
+
|
|
1188
|
+
return ""
|
|
1189
|
+
|
|
1155
1190
|
def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
|
|
1156
1191
|
return self.sql(expression, "this")
|
|
1157
1192
|
|
|
@@ -1302,8 +1337,9 @@ class Generator(metaclass=_Generator):
|
|
|
1302
1337
|
partition = f" {partition}" if partition else ""
|
|
1303
1338
|
format = self.sql(expression, "format")
|
|
1304
1339
|
format = f" {format}" if format else ""
|
|
1340
|
+
as_json = " AS JSON" if expression.args.get("as_json") else ""
|
|
1305
1341
|
|
|
1306
|
-
return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
|
|
1342
|
+
return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
|
|
1307
1343
|
|
|
1308
1344
|
def heredoc_sql(self, expression: exp.Heredoc) -> str:
|
|
1309
1345
|
tag = self.sql(expression, "tag")
|
|
@@ -1398,6 +1434,7 @@ class Generator(metaclass=_Generator):
|
|
|
1398
1434
|
escape_backslash=False,
|
|
1399
1435
|
delimiter=self.dialect.BYTE_END,
|
|
1400
1436
|
escaped_delimiter=self._escaped_byte_quote_end,
|
|
1437
|
+
is_byte_string=True,
|
|
1401
1438
|
)
|
|
1402
1439
|
is_bytes = expression.args.get("is_bytes", False)
|
|
1403
1440
|
delimited_byte_string = (
|
|
@@ -1455,7 +1492,15 @@ class Generator(metaclass=_Generator):
|
|
|
1455
1492
|
def datatype_sql(self, expression: exp.DataType) -> str:
|
|
1456
1493
|
nested = ""
|
|
1457
1494
|
values = ""
|
|
1458
|
-
|
|
1495
|
+
|
|
1496
|
+
expr_nested = expression.args.get("nested")
|
|
1497
|
+
interior = (
|
|
1498
|
+
self.expressions(
|
|
1499
|
+
expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
|
|
1500
|
+
)
|
|
1501
|
+
if expr_nested and self.pretty
|
|
1502
|
+
else self.expressions(expression, flat=True)
|
|
1503
|
+
)
|
|
1459
1504
|
|
|
1460
1505
|
type_value = expression.this
|
|
1461
1506
|
if type_value in self.UNSUPPORTED_TYPES:
|
|
@@ -1473,7 +1518,7 @@ class Generator(metaclass=_Generator):
|
|
|
1473
1518
|
)
|
|
1474
1519
|
|
|
1475
1520
|
if interior:
|
|
1476
|
-
if
|
|
1521
|
+
if expr_nested:
|
|
1477
1522
|
nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
|
|
1478
1523
|
if expression.args.get("values") is not None:
|
|
1479
1524
|
delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
|
|
@@ -2019,7 +2064,12 @@ class Generator(metaclass=_Generator):
|
|
|
2019
2064
|
constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
|
|
2020
2065
|
|
|
2021
2066
|
conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
|
|
2022
|
-
|
|
2067
|
+
if conflict_keys:
|
|
2068
|
+
conflict_keys = f"({conflict_keys})"
|
|
2069
|
+
|
|
2070
|
+
index_predicate = self.sql(expression, "index_predicate")
|
|
2071
|
+
conflict_keys = f"{conflict_keys}{index_predicate} "
|
|
2072
|
+
|
|
2023
2073
|
action = self.sql(expression, "action")
|
|
2024
2074
|
|
|
2025
2075
|
expressions = self.expressions(expression, flat=True)
|
|
@@ -2222,10 +2272,42 @@ class Generator(metaclass=_Generator):
|
|
|
2222
2272
|
def tuple_sql(self, expression: exp.Tuple) -> str:
|
|
2223
2273
|
return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
|
|
2224
2274
|
|
|
2275
|
+
def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
|
|
2276
|
+
"""
|
|
2277
|
+
Returns (join_sql, from_sql) for UPDATE statements.
|
|
2278
|
+
- join_sql: placed after UPDATE table, before SET
|
|
2279
|
+
- from_sql: placed after SET clause (standard position)
|
|
2280
|
+
Dialects like MySQL need to convert FROM to JOIN syntax.
|
|
2281
|
+
"""
|
|
2282
|
+
if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
|
|
2283
|
+
return ("", self.sql(expression, "from_"))
|
|
2284
|
+
|
|
2285
|
+
# Qualify unqualified columns in SET clause with the target table
|
|
2286
|
+
# MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
|
|
2287
|
+
target_table = expression.this
|
|
2288
|
+
if isinstance(target_table, exp.Table):
|
|
2289
|
+
target_name = exp.to_identifier(target_table.alias_or_name)
|
|
2290
|
+
for eq in expression.expressions:
|
|
2291
|
+
col = eq.this
|
|
2292
|
+
if isinstance(col, exp.Column) and not col.table:
|
|
2293
|
+
col.set("table", target_name)
|
|
2294
|
+
|
|
2295
|
+
table = from_expr.this
|
|
2296
|
+
if nested_joins := table.args.get("joins", []):
|
|
2297
|
+
table.set("joins", None)
|
|
2298
|
+
|
|
2299
|
+
join_sql = self.sql(exp.Join(this=table, on=exp.true()))
|
|
2300
|
+
for nested in nested_joins:
|
|
2301
|
+
if not nested.args.get("on") and not nested.args.get("using"):
|
|
2302
|
+
nested.set("on", exp.true())
|
|
2303
|
+
join_sql += self.sql(nested)
|
|
2304
|
+
|
|
2305
|
+
return (join_sql, "")
|
|
2306
|
+
|
|
2225
2307
|
def update_sql(self, expression: exp.Update) -> str:
|
|
2226
2308
|
this = self.sql(expression, "this")
|
|
2309
|
+
join_sql, from_sql = self._update_from_joins_sql(expression)
|
|
2227
2310
|
set_sql = self.expressions(expression, flat=True)
|
|
2228
|
-
from_sql = self.sql(expression, "from_")
|
|
2229
2311
|
where_sql = self.sql(expression, "where")
|
|
2230
2312
|
returning = self.sql(expression, "returning")
|
|
2231
2313
|
order = self.sql(expression, "order")
|
|
@@ -2236,7 +2318,7 @@ class Generator(metaclass=_Generator):
|
|
|
2236
2318
|
expression_sql = f"{returning}{from_sql}{where_sql}"
|
|
2237
2319
|
options = self.expressions(expression, key="options")
|
|
2238
2320
|
options = f" OPTION({options})" if options else ""
|
|
2239
|
-
sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}"
|
|
2321
|
+
sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
|
|
2240
2322
|
return self.prepend_ctes(expression, sql)
|
|
2241
2323
|
|
|
2242
2324
|
def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
|
|
@@ -2303,6 +2385,24 @@ class Generator(metaclass=_Generator):
|
|
|
2303
2385
|
expressions = self.expressions(expression, indent=False)
|
|
2304
2386
|
return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
|
|
2305
2387
|
|
|
2388
|
+
def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
|
|
2389
|
+
this = self.sql(expression, "this")
|
|
2390
|
+
|
|
2391
|
+
columns = self.expressions(expression, flat=True)
|
|
2392
|
+
|
|
2393
|
+
from_sql = self.sql(expression, "from_index")
|
|
2394
|
+
from_sql = f" FROM {from_sql}" if from_sql else ""
|
|
2395
|
+
|
|
2396
|
+
properties = expression.args.get("properties")
|
|
2397
|
+
properties_sql = (
|
|
2398
|
+
f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
|
|
2399
|
+
)
|
|
2400
|
+
|
|
2401
|
+
return f"{this}({columns}){from_sql}{properties_sql}"
|
|
2402
|
+
|
|
2403
|
+
def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
|
|
2404
|
+
return f"ROLLUP ({self.expressions(expression, flat=True)})"
|
|
2405
|
+
|
|
2306
2406
|
def cube_sql(self, expression: exp.Cube) -> str:
|
|
2307
2407
|
expressions = self.expressions(expression, indent=False)
|
|
2308
2408
|
return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
|
|
@@ -2368,6 +2468,7 @@ class Generator(metaclass=_Generator):
|
|
|
2368
2468
|
side,
|
|
2369
2469
|
expression.kind,
|
|
2370
2470
|
expression.hint if self.JOIN_HINTS else None,
|
|
2471
|
+
"DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
|
|
2371
2472
|
)
|
|
2372
2473
|
if op
|
|
2373
2474
|
)
|
|
@@ -2533,11 +2634,17 @@ class Generator(metaclass=_Generator):
|
|
|
2533
2634
|
escape_backslash: bool = True,
|
|
2534
2635
|
delimiter: t.Optional[str] = None,
|
|
2535
2636
|
escaped_delimiter: t.Optional[str] = None,
|
|
2637
|
+
is_byte_string: bool = False,
|
|
2536
2638
|
) -> str:
|
|
2537
|
-
if
|
|
2538
|
-
|
|
2639
|
+
if is_byte_string:
|
|
2640
|
+
supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
|
|
2641
|
+
else:
|
|
2642
|
+
supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
|
|
2643
|
+
|
|
2644
|
+
if supports_escape_sequences:
|
|
2539
2645
|
text = "".join(
|
|
2540
|
-
|
|
2646
|
+
self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
|
|
2647
|
+
for ch in text
|
|
2541
2648
|
)
|
|
2542
2649
|
|
|
2543
2650
|
delimiter = delimiter or self.dialect.QUOTE_END
|
|
@@ -3509,6 +3616,10 @@ class Generator(metaclass=_Generator):
|
|
|
3509
3616
|
default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
|
|
3510
3617
|
return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
|
|
3511
3618
|
|
|
3619
|
+
# Base implementation that excludes safe, zone, and target_type metadata args
|
|
3620
|
+
def strtotime_sql(self, expression: exp.StrToTime) -> str:
|
|
3621
|
+
return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
|
|
3622
|
+
|
|
3512
3623
|
def currentdate_sql(self, expression: exp.CurrentDate) -> str:
|
|
3513
3624
|
zone = self.sql(expression, "this")
|
|
3514
3625
|
return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
|
|
@@ -4435,11 +4546,11 @@ class Generator(metaclass=_Generator):
|
|
|
4435
4546
|
def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
|
|
4436
4547
|
this = expression.this
|
|
4437
4548
|
time_format = self.format_time(expression)
|
|
4438
|
-
|
|
4549
|
+
safe = expression.args.get("safe")
|
|
4439
4550
|
if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
|
|
4440
4551
|
return self.sql(
|
|
4441
4552
|
exp.cast(
|
|
4442
|
-
exp.StrToTime(this=this, format=expression.args["format"]),
|
|
4553
|
+
exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
|
|
4443
4554
|
exp.DataType.Type.DATE,
|
|
4444
4555
|
)
|
|
4445
4556
|
)
|
|
@@ -4447,6 +4558,9 @@ class Generator(metaclass=_Generator):
|
|
|
4447
4558
|
if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
|
|
4448
4559
|
return self.sql(this)
|
|
4449
4560
|
|
|
4561
|
+
if safe:
|
|
4562
|
+
return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE)))
|
|
4563
|
+
|
|
4450
4564
|
return self.sql(exp.cast(this, exp.DataType.Type.DATE))
|
|
4451
4565
|
|
|
4452
4566
|
def unixdate_sql(self, expression: exp.UnixDate) -> str:
|
|
@@ -4811,18 +4925,6 @@ class Generator(metaclass=_Generator):
|
|
|
4811
4925
|
|
|
4812
4926
|
return self.sql(generate_series)
|
|
4813
4927
|
|
|
4814
|
-
def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
|
|
4815
|
-
exprs = expression.expressions
|
|
4816
|
-
if not self.ARRAY_CONCAT_IS_VAR_LEN:
|
|
4817
|
-
if len(exprs) == 0:
|
|
4818
|
-
rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[])
|
|
4819
|
-
else:
|
|
4820
|
-
rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
|
|
4821
|
-
else:
|
|
4822
|
-
rhs = self.expressions(expression) # type: ignore
|
|
4823
|
-
|
|
4824
|
-
return self.func(name, expression.this, rhs or None)
|
|
4825
|
-
|
|
4826
4928
|
def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
|
|
4827
4929
|
if self.SUPPORTS_CONVERT_TIMEZONE:
|
|
4828
4930
|
return self.function_fallback_sql(expression)
|
|
@@ -4929,30 +5031,49 @@ class Generator(metaclass=_Generator):
|
|
|
4929
5031
|
|
|
4930
5032
|
return self.func("JSON_EXISTS", this, path)
|
|
4931
5033
|
|
|
4932
|
-
def
|
|
4933
|
-
|
|
5034
|
+
def _add_arrayagg_null_filter(
|
|
5035
|
+
self,
|
|
5036
|
+
array_agg_sql: str,
|
|
5037
|
+
array_agg_expr: exp.ArrayAgg,
|
|
5038
|
+
column_expr: exp.Expression,
|
|
5039
|
+
) -> str:
|
|
5040
|
+
"""
|
|
5041
|
+
Add NULL filter to ARRAY_AGG if dialect requires it.
|
|
5042
|
+
|
|
5043
|
+
Args:
|
|
5044
|
+
array_agg_sql: The generated ARRAY_AGG SQL string
|
|
5045
|
+
array_agg_expr: The ArrayAgg expression node
|
|
5046
|
+
column_expr: The column/expression to filter (before ORDER BY wrapping)
|
|
4934
5047
|
|
|
5048
|
+
Returns:
|
|
5049
|
+
SQL string with FILTER clause added if needed
|
|
5050
|
+
"""
|
|
4935
5051
|
# Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
|
|
4936
5052
|
# on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
|
|
4937
|
-
if
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
5053
|
+
if not (
|
|
5054
|
+
self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
|
|
5055
|
+
):
|
|
5056
|
+
return array_agg_sql
|
|
5057
|
+
|
|
5058
|
+
parent = array_agg_expr.parent
|
|
5059
|
+
if isinstance(parent, exp.Filter):
|
|
5060
|
+
parent_cond = parent.expression.this
|
|
5061
|
+
parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
|
|
5062
|
+
elif column_expr.find(exp.Column):
|
|
5063
|
+
# Do not add the filter if the input is not a column (e.g. literal, struct etc)
|
|
5064
|
+
# DISTINCT is already present in the agg function, do not propagate it to FILTER as well
|
|
5065
|
+
this_sql = (
|
|
5066
|
+
self.expressions(column_expr)
|
|
5067
|
+
if isinstance(column_expr, exp.Distinct)
|
|
5068
|
+
else self.sql(column_expr)
|
|
5069
|
+
)
|
|
5070
|
+
array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
|
|
4952
5071
|
|
|
4953
|
-
|
|
5072
|
+
return array_agg_sql
|
|
4954
5073
|
|
|
4955
|
-
|
|
5074
|
+
def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
|
|
5075
|
+
array_agg = self.function_fallback_sql(expression)
|
|
5076
|
+
return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
|
|
4956
5077
|
|
|
4957
5078
|
def slice_sql(self, expression: exp.Slice) -> str:
|
|
4958
5079
|
step = self.sql(expression, "step")
|
|
@@ -5044,7 +5165,8 @@ class Generator(metaclass=_Generator):
|
|
|
5044
5165
|
|
|
5045
5166
|
@unsupported_args("format")
|
|
5046
5167
|
def todouble_sql(self, expression: exp.ToDouble) -> str:
|
|
5047
|
-
|
|
5168
|
+
cast = exp.TryCast if expression.args.get("safe") else exp.Cast
|
|
5169
|
+
return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE)))
|
|
5048
5170
|
|
|
5049
5171
|
def string_sql(self, expression: exp.String) -> str:
|
|
5050
5172
|
this = expression.this
|
|
@@ -5154,7 +5276,8 @@ class Generator(metaclass=_Generator):
|
|
|
5154
5276
|
return include
|
|
5155
5277
|
|
|
5156
5278
|
def xmlelement_sql(self, expression: exp.XMLElement) -> str:
|
|
5157
|
-
|
|
5279
|
+
prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
|
|
5280
|
+
name = f"{prefix} {self.sql(expression, 'this')}"
|
|
5158
5281
|
return self.func("XMLELEMENT", name, *expression.expressions)
|
|
5159
5282
|
|
|
5160
5283
|
def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
|
|
@@ -5467,3 +5590,9 @@ class Generator(metaclass=_Generator):
|
|
|
5467
5590
|
return "WEEK"
|
|
5468
5591
|
|
|
5469
5592
|
return self.func("WEEK", expression.this)
|
|
5593
|
+
|
|
5594
|
+
def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
|
|
5595
|
+
this = self.expressions(expression)
|
|
5596
|
+
charset = self.sql(expression, "charset")
|
|
5597
|
+
using = f" USING {charset}" if charset else ""
|
|
5598
|
+
return self.func(name, this + using)
|