sqlglot 27.29.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.
Files changed (63) hide show
  1. sqlglot/__main__.py +6 -4
  2. sqlglot/_version.py +2 -2
  3. sqlglot/dialects/bigquery.py +116 -295
  4. sqlglot/dialects/clickhouse.py +67 -2
  5. sqlglot/dialects/databricks.py +38 -1
  6. sqlglot/dialects/dialect.py +327 -286
  7. sqlglot/dialects/dremio.py +4 -1
  8. sqlglot/dialects/duckdb.py +718 -22
  9. sqlglot/dialects/exasol.py +243 -10
  10. sqlglot/dialects/hive.py +8 -8
  11. sqlglot/dialects/mysql.py +11 -2
  12. sqlglot/dialects/oracle.py +29 -0
  13. sqlglot/dialects/postgres.py +46 -24
  14. sqlglot/dialects/presto.py +47 -16
  15. sqlglot/dialects/redshift.py +16 -0
  16. sqlglot/dialects/risingwave.py +3 -0
  17. sqlglot/dialects/singlestore.py +12 -3
  18. sqlglot/dialects/snowflake.py +199 -271
  19. sqlglot/dialects/spark.py +2 -2
  20. sqlglot/dialects/spark2.py +11 -48
  21. sqlglot/dialects/sqlite.py +9 -0
  22. sqlglot/dialects/teradata.py +5 -8
  23. sqlglot/dialects/trino.py +6 -0
  24. sqlglot/dialects/tsql.py +61 -25
  25. sqlglot/diff.py +4 -2
  26. sqlglot/errors.py +69 -0
  27. sqlglot/expressions.py +484 -84
  28. sqlglot/generator.py +143 -41
  29. sqlglot/helper.py +2 -2
  30. sqlglot/optimizer/annotate_types.py +247 -140
  31. sqlglot/optimizer/canonicalize.py +6 -1
  32. sqlglot/optimizer/eliminate_joins.py +1 -1
  33. sqlglot/optimizer/eliminate_subqueries.py +2 -2
  34. sqlglot/optimizer/merge_subqueries.py +5 -5
  35. sqlglot/optimizer/normalize.py +20 -13
  36. sqlglot/optimizer/normalize_identifiers.py +17 -3
  37. sqlglot/optimizer/optimizer.py +4 -0
  38. sqlglot/optimizer/pushdown_predicates.py +1 -1
  39. sqlglot/optimizer/qualify.py +14 -6
  40. sqlglot/optimizer/qualify_columns.py +113 -352
  41. sqlglot/optimizer/qualify_tables.py +112 -70
  42. sqlglot/optimizer/resolver.py +374 -0
  43. sqlglot/optimizer/scope.py +27 -16
  44. sqlglot/optimizer/simplify.py +1074 -964
  45. sqlglot/optimizer/unnest_subqueries.py +12 -2
  46. sqlglot/parser.py +276 -160
  47. sqlglot/planner.py +2 -2
  48. sqlglot/schema.py +15 -4
  49. sqlglot/tokens.py +42 -7
  50. sqlglot/transforms.py +77 -22
  51. sqlglot/typing/__init__.py +316 -0
  52. sqlglot/typing/bigquery.py +376 -0
  53. sqlglot/typing/hive.py +12 -0
  54. sqlglot/typing/presto.py +24 -0
  55. sqlglot/typing/snowflake.py +505 -0
  56. sqlglot/typing/spark2.py +58 -0
  57. sqlglot/typing/tsql.py +9 -0
  58. {sqlglot-27.29.0.dist-info → sqlglot-28.4.0.dist-info}/METADATA +2 -2
  59. sqlglot-28.4.0.dist-info/RECORD +92 -0
  60. sqlglot-27.29.0.dist-info/RECORD +0 -84
  61. {sqlglot-27.29.0.dist-info → sqlglot-28.4.0.dist-info}/WHEEL +0 -0
  62. {sqlglot-27.29.0.dist-info → sqlglot-28.4.0.dist-info}/licenses/LICENSE +0 -0
  63. {sqlglot-27.29.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 or 'always': Always quote.
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, "with")
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
- return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
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.can_identify(text, self.identify)
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("with") else sql
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
- return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
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
- return f"UNPIVOT {this}"
2155
-
2156
- on = f"{self.seg('ON')} {expressions}"
2157
- into = self.sql(expression, "into")
2158
- into = f"{self.seg('INTO')} {into}" if into else ""
2159
- using = self.expressions(expression, key="using", flat=True)
2160
- using = f"{self.seg('USING')} {using}" if using else ""
2161
- return f"{direction} {this}{on}{into}{using}{group}"
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
- return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
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, "from")
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")
@@ -2334,7 +2364,7 @@ class Generator(metaclass=_Generator):
2334
2364
  op
2335
2365
  for op in (
2336
2366
  expression.method,
2337
- "GLOBAL" if expression.args.get("global") else None,
2367
+ "GLOBAL" if expression.args.get("global_") else None,
2338
2368
  side,
2339
2369
  expression.kind,
2340
2370
  expression.hint if self.JOIN_HINTS else None,
@@ -2441,12 +2471,15 @@ class Generator(metaclass=_Generator):
2441
2471
 
2442
2472
  def setitem_sql(self, expression: exp.SetItem) -> str:
2443
2473
  kind = self.sql(expression, "kind")
2444
- kind = f"{kind} " if kind else ""
2474
+ if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2475
+ kind = ""
2476
+ else:
2477
+ kind = f"{kind} " if kind else ""
2445
2478
  this = self.sql(expression, "this")
2446
2479
  expressions = self.expressions(expression)
2447
2480
  collate = self.sql(expression, "collate")
2448
2481
  collate = f" COLLATE {collate}" if collate else ""
2449
- global_ = "GLOBAL " if expression.args.get("global") else ""
2482
+ global_ = "GLOBAL " if expression.args.get("global_") else ""
2450
2483
  return f"{global_}{kind}{this}{expressions}{collate}"
2451
2484
 
2452
2485
  def set_sql(self, expression: exp.Set) -> str:
@@ -2544,7 +2577,7 @@ class Generator(metaclass=_Generator):
2544
2577
  return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2545
2578
 
2546
2579
  def withfill_sql(self, expression: exp.WithFill) -> str:
2547
- from_sql = self.sql(expression, "from")
2580
+ from_sql = self.sql(expression, "from_")
2548
2581
  from_sql = f" FROM {from_sql}" if from_sql else ""
2549
2582
  to_sql = self.sql(expression, "to")
2550
2583
  to_sql = f" TO {to_sql}" if to_sql else ""
@@ -2705,7 +2738,7 @@ class Generator(metaclass=_Generator):
2705
2738
  return f" {options}" if options else ""
2706
2739
 
2707
2740
  def for_modifiers(self, expression: exp.Expression) -> str:
2708
- for_modifiers = self.expressions(expression, key="for")
2741
+ for_modifiers = self.expressions(expression, key="for_")
2709
2742
  return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2710
2743
 
2711
2744
  def queryoption_sql(self, expression: exp.QueryOption) -> str:
@@ -2776,11 +2809,11 @@ class Generator(metaclass=_Generator):
2776
2809
  expression,
2777
2810
  f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2778
2811
  self.sql(expression, "into", comment=False),
2779
- self.sql(expression, "from", comment=False),
2812
+ self.sql(expression, "from_", comment=False),
2780
2813
  )
2781
2814
 
2782
2815
  # If both the CTE and SELECT clauses have comments, generate the latter earlier
2783
- if expression.args.get("with"):
2816
+ if expression.args.get("with_"):
2784
2817
  sql = self.maybe_comment(sql, expression)
2785
2818
  expression.pop_comments()
2786
2819
 
@@ -2808,7 +2841,7 @@ class Generator(metaclass=_Generator):
2808
2841
  return ""
2809
2842
 
2810
2843
  def star_sql(self, expression: exp.Star) -> str:
2811
- except_ = self.expressions(expression, key="except", flat=True)
2844
+ except_ = self.expressions(expression, key="except_", flat=True)
2812
2845
  except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2813
2846
  replace = self.expressions(expression, key="replace", flat=True)
2814
2847
  replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
@@ -3068,6 +3101,14 @@ class Generator(metaclass=_Generator):
3068
3101
  return args
3069
3102
 
3070
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
+
3071
3112
  expressions = self.convert_concat_args(expression)
3072
3113
 
3073
3114
  # Some dialects don't allow a single-argument CONCAT call
@@ -3099,11 +3140,13 @@ class Generator(metaclass=_Generator):
3099
3140
  return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3100
3141
 
3101
3142
  def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3143
+ this = self.sql(expression, "this")
3144
+ this = f" {this}" if this else ""
3102
3145
  expressions = self.expressions(expression, flat=True)
3103
3146
  include = self.sql(expression, "include")
3104
3147
  options = self.expressions(expression, key="options", flat=True, sep=" ")
3105
3148
  options = f" {options}" if options else ""
3106
- return f"PRIMARY KEY ({expressions}){include}{options}"
3149
+ return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3107
3150
 
3108
3151
  def if_sql(self, expression: exp.If) -> str:
3109
3152
  return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
@@ -3801,6 +3844,10 @@ class Generator(metaclass=_Generator):
3801
3844
  for expr in exprs[1:]:
3802
3845
  like_expr = connective(like_expr, exp_class(this=this, expression=expr))
3803
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
+
3804
3851
  return self.sql(like_expr)
3805
3852
 
3806
3853
  return self.binary(expression, op)
@@ -3811,6 +3858,9 @@ class Generator(metaclass=_Generator):
3811
3858
  def ilike_sql(self, expression: exp.ILike) -> str:
3812
3859
  return self._like_sql(expression)
3813
3860
 
3861
+ def match_sql(self, expression: exp.Match) -> str:
3862
+ return self.binary(expression, "MATCH")
3863
+
3814
3864
  def similarto_sql(self, expression: exp.SimilarTo) -> str:
3815
3865
  return self.binary(expression, "SIMILAR TO")
3816
3866
 
@@ -3835,9 +3885,6 @@ class Generator(metaclass=_Generator):
3835
3885
  def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3836
3886
  return self.binary(expression, "IS DISTINCT FROM")
3837
3887
 
3838
- def slice_sql(self, expression: exp.Slice) -> str:
3839
- return self.binary(expression, ":")
3840
-
3841
3888
  def sub_sql(self, expression: exp.Sub) -> str:
3842
3889
  return self.binary(expression, "-")
3843
3890
 
@@ -4065,7 +4112,9 @@ class Generator(metaclass=_Generator):
4065
4112
  if isinstance(then_expression.args.get("expressions"), exp.Star):
4066
4113
  then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4067
4114
  else:
4068
- then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
4115
+ expressions_sql = self.expressions(then_expression)
4116
+ then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4117
+
4069
4118
  else:
4070
4119
  then = self.sql(then_expression)
4071
4120
  return f"WHEN {matched}{source}{condition} THEN {then}"
@@ -4336,8 +4385,8 @@ class Generator(metaclass=_Generator):
4336
4385
 
4337
4386
  def refresh_sql(self, expression: exp.Refresh) -> str:
4338
4387
  this = self.sql(expression, "this")
4339
- table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4340
- return f"REFRESH {table}{this}"
4388
+ kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4389
+ return f"REFRESH {kind}{this}"
4341
4390
 
4342
4391
  def toarray_sql(self, expression: exp.ToArray) -> str:
4343
4392
  arg = expression.this
@@ -4795,7 +4844,7 @@ class Generator(metaclass=_Generator):
4795
4844
  this = self.sql(expression, "this")
4796
4845
  this = f" {this}" if this else ""
4797
4846
 
4798
- _with = expression.args.get("with")
4847
+ _with = expression.args.get("with_")
4799
4848
 
4800
4849
  if _with is None:
4801
4850
  with_sql = ""
@@ -4905,6 +4954,14 @@ class Generator(metaclass=_Generator):
4905
4954
 
4906
4955
  return array_agg
4907
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
+
4908
4965
  def apply_sql(self, expression: exp.Apply) -> str:
4909
4966
  this = self.sql(expression, "this")
4910
4967
  expr = self.sql(expression, "expression")
@@ -4979,8 +5036,8 @@ class Generator(metaclass=_Generator):
4979
5036
  def overlay_sql(self, expression: exp.Overlay):
4980
5037
  this = self.sql(expression, "this")
4981
5038
  expr = self.sql(expression, "expression")
4982
- from_sql = self.sql(expression, "from")
4983
- for_sql = self.sql(expression, "for")
5039
+ from_sql = self.sql(expression, "from_")
5040
+ for_sql = self.sql(expression, "for_")
4984
5041
  for_sql = f" FOR {for_sql}" if for_sql else ""
4985
5042
 
4986
5043
  return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@@ -5311,9 +5368,11 @@ class Generator(metaclass=_Generator):
5311
5368
  expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5312
5369
  )
5313
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 ""
5314
5373
  where = self.sql(expression, "where")
5315
5374
  where = self.seg(f"WHERE {where}") if where else ""
5316
- body = self.indent(this + metrics + dimensions + where, skip_first=True)
5375
+ body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5317
5376
  return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5318
5377
 
5319
5378
  def getextract_sql(self, expression: exp.GetExtract) -> str:
@@ -5365,3 +5424,46 @@ class Generator(metaclass=_Generator):
5365
5424
 
5366
5425
  def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5367
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
@@ -139,7 +139,7 @@ def csv(*args: str, sep: str = ", ") -> str:
139
139
  def subclasses(
140
140
  module_name: str,
141
141
  classes: t.Type | t.Tuple[t.Type, ...],
142
- exclude: t.Type | t.Tuple[t.Type, ...] = (),
142
+ exclude: t.Set[t.Type] = set(),
143
143
  ) -> t.List[t.Type]:
144
144
  """
145
145
  Returns all subclasses for a collection of classes, possibly excluding some of them.
@@ -147,7 +147,7 @@ def subclasses(
147
147
  Args:
148
148
  module_name: The name of the module to search for subclasses in.
149
149
  classes: Class(es) we want to find the subclasses of.
150
- exclude: Class(es) we want to exclude from the returned list.
150
+ exclude: Classes we want to exclude from the returned list.
151
151
 
152
152
  Returns:
153
153
  The target subclasses.