sqlglot 28.4.1__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.
Files changed (50) hide show
  1. sqlglot/_version.py +2 -2
  2. sqlglot/dialects/bigquery.py +20 -23
  3. sqlglot/dialects/clickhouse.py +2 -0
  4. sqlglot/dialects/dialect.py +355 -18
  5. sqlglot/dialects/doris.py +38 -90
  6. sqlglot/dialects/druid.py +1 -0
  7. sqlglot/dialects/duckdb.py +1739 -163
  8. sqlglot/dialects/exasol.py +17 -1
  9. sqlglot/dialects/hive.py +27 -2
  10. sqlglot/dialects/mysql.py +103 -11
  11. sqlglot/dialects/oracle.py +38 -1
  12. sqlglot/dialects/postgres.py +142 -33
  13. sqlglot/dialects/presto.py +6 -2
  14. sqlglot/dialects/redshift.py +7 -1
  15. sqlglot/dialects/singlestore.py +13 -3
  16. sqlglot/dialects/snowflake.py +271 -21
  17. sqlglot/dialects/spark.py +25 -0
  18. sqlglot/dialects/spark2.py +4 -3
  19. sqlglot/dialects/starrocks.py +152 -17
  20. sqlglot/dialects/trino.py +1 -0
  21. sqlglot/dialects/tsql.py +5 -0
  22. sqlglot/diff.py +1 -1
  23. sqlglot/expressions.py +239 -47
  24. sqlglot/generator.py +173 -44
  25. sqlglot/optimizer/annotate_types.py +129 -60
  26. sqlglot/optimizer/merge_subqueries.py +13 -2
  27. sqlglot/optimizer/qualify_columns.py +7 -0
  28. sqlglot/optimizer/resolver.py +19 -0
  29. sqlglot/optimizer/scope.py +12 -0
  30. sqlglot/optimizer/unnest_subqueries.py +7 -0
  31. sqlglot/parser.py +251 -58
  32. sqlglot/schema.py +186 -14
  33. sqlglot/tokens.py +36 -6
  34. sqlglot/transforms.py +6 -5
  35. sqlglot/typing/__init__.py +29 -10
  36. sqlglot/typing/bigquery.py +5 -10
  37. sqlglot/typing/duckdb.py +39 -0
  38. sqlglot/typing/hive.py +50 -1
  39. sqlglot/typing/mysql.py +32 -0
  40. sqlglot/typing/presto.py +0 -1
  41. sqlglot/typing/snowflake.py +80 -17
  42. sqlglot/typing/spark.py +29 -0
  43. sqlglot/typing/spark2.py +9 -1
  44. sqlglot/typing/tsql.py +21 -0
  45. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/METADATA +47 -2
  46. sqlglot-28.8.0.dist-info/RECORD +95 -0
  47. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/WHEEL +1 -1
  48. sqlglot-28.4.1.dist-info/RECORD +0 -92
  49. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/licenses/LICENSE +0 -0
  50. {sqlglot-28.4.1.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
- interior = self.expressions(expression, flat=True)
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 expression.args.get("nested"):
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
- conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
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 self.dialect.ESCAPED_SEQUENCES:
2538
- to_escaped = self.dialect.ESCAPED_SEQUENCES
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
- to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
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 arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4933
- array_agg = self.function_fallback_sql(expression)
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 self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4938
- parent = expression.parent
4939
- if isinstance(parent, exp.Filter):
4940
- parent_cond = parent.expression.this
4941
- parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4942
- else:
4943
- this = expression.this
4944
- # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4945
- if this.find(exp.Column):
4946
- # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4947
- this_sql = (
4948
- self.expressions(this)
4949
- if isinstance(this, exp.Distinct)
4950
- else self.sql(expression, "this")
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
- array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
5072
+ return array_agg_sql
4954
5073
 
4955
- return array_agg
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
- return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
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
- name = f"NAME {self.sql(expression, 'this')}"
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)