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.
Files changed (68) hide show
  1. sqlglot/__init__.py +1 -0
  2. sqlglot/__main__.py +6 -4
  3. sqlglot/_version.py +2 -2
  4. sqlglot/dialects/bigquery.py +118 -279
  5. sqlglot/dialects/clickhouse.py +73 -5
  6. sqlglot/dialects/databricks.py +38 -1
  7. sqlglot/dialects/dialect.py +354 -275
  8. sqlglot/dialects/dremio.py +4 -1
  9. sqlglot/dialects/duckdb.py +754 -25
  10. sqlglot/dialects/exasol.py +243 -10
  11. sqlglot/dialects/hive.py +8 -8
  12. sqlglot/dialects/mysql.py +14 -4
  13. sqlglot/dialects/oracle.py +29 -0
  14. sqlglot/dialects/postgres.py +60 -26
  15. sqlglot/dialects/presto.py +47 -16
  16. sqlglot/dialects/redshift.py +16 -0
  17. sqlglot/dialects/risingwave.py +3 -0
  18. sqlglot/dialects/singlestore.py +12 -3
  19. sqlglot/dialects/snowflake.py +239 -218
  20. sqlglot/dialects/spark.py +15 -4
  21. sqlglot/dialects/spark2.py +11 -48
  22. sqlglot/dialects/sqlite.py +10 -0
  23. sqlglot/dialects/starrocks.py +3 -0
  24. sqlglot/dialects/teradata.py +5 -8
  25. sqlglot/dialects/trino.py +6 -0
  26. sqlglot/dialects/tsql.py +61 -22
  27. sqlglot/diff.py +4 -2
  28. sqlglot/errors.py +69 -0
  29. sqlglot/executor/__init__.py +5 -10
  30. sqlglot/executor/python.py +1 -29
  31. sqlglot/expressions.py +637 -100
  32. sqlglot/generator.py +160 -43
  33. sqlglot/helper.py +2 -44
  34. sqlglot/lineage.py +10 -4
  35. sqlglot/optimizer/annotate_types.py +247 -140
  36. sqlglot/optimizer/canonicalize.py +6 -1
  37. sqlglot/optimizer/eliminate_joins.py +1 -1
  38. sqlglot/optimizer/eliminate_subqueries.py +2 -2
  39. sqlglot/optimizer/merge_subqueries.py +5 -5
  40. sqlglot/optimizer/normalize.py +20 -13
  41. sqlglot/optimizer/normalize_identifiers.py +17 -3
  42. sqlglot/optimizer/optimizer.py +4 -0
  43. sqlglot/optimizer/pushdown_predicates.py +1 -1
  44. sqlglot/optimizer/qualify.py +18 -10
  45. sqlglot/optimizer/qualify_columns.py +122 -275
  46. sqlglot/optimizer/qualify_tables.py +128 -76
  47. sqlglot/optimizer/resolver.py +374 -0
  48. sqlglot/optimizer/scope.py +27 -16
  49. sqlglot/optimizer/simplify.py +1075 -959
  50. sqlglot/optimizer/unnest_subqueries.py +12 -2
  51. sqlglot/parser.py +296 -170
  52. sqlglot/planner.py +2 -2
  53. sqlglot/schema.py +15 -4
  54. sqlglot/tokens.py +42 -7
  55. sqlglot/transforms.py +77 -22
  56. sqlglot/typing/__init__.py +316 -0
  57. sqlglot/typing/bigquery.py +376 -0
  58. sqlglot/typing/hive.py +12 -0
  59. sqlglot/typing/presto.py +24 -0
  60. sqlglot/typing/snowflake.py +505 -0
  61. sqlglot/typing/spark2.py +58 -0
  62. sqlglot/typing/tsql.py +9 -0
  63. {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/METADATA +2 -2
  64. sqlglot-28.4.0.dist-info/RECORD +92 -0
  65. sqlglot-27.27.0.dist-info/RECORD +0 -84
  66. {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/WHEEL +0 -0
  67. {sqlglot-27.27.0.dist-info → sqlglot-28.4.0.dist-info}/licenses/LICENSE +0 -0
  68. {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 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")
@@ -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("global") else None,
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
- 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 ""
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("global") else ""
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, "from")
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="for")
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, "from", comment=False),
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("with"):
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="except", flat=True)
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
- 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
+
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
- table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4325
- return f"REFRESH {table}{this}"
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("with")
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, "from")
4968
- for_sql = self.sql(expression, "for")
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.Type | t.Tuple[t.Type, ...] = (),
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: Class(es) we want to exclude from the returned list.
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 or not to clean up selects by trimming to only relevant columns.
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
- {k: t.cast(exp.Query, maybe_parse(v, dialect=dialect)) for k, v in sources.items()},
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.is_star:
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