sqlglotc 30.17.0__tar.gz → 30.18.0__tar.gz

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 (119) hide show
  1. {sqlglotc-30.17.0/sqlglotc.egg-info → sqlglotc-30.18.0}/PKG-INFO +3 -3
  2. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/pyproject.toml +2 -2
  3. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/executor/table.py +1 -1
  4. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/builders.py +9 -4
  5. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/constraints.py +4 -0
  6. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/ddl.py +1 -1
  7. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/json.py +4 -0
  8. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/query.py +2 -1
  9. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/string.py +2 -1
  10. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generator.py +50 -8
  11. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/clickhouse.py +2 -6
  12. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/dremio.py +3 -0
  13. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/duckdb.py +7 -9
  14. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/hive.py +1 -0
  15. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/oracle.py +2 -1
  16. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/postgres.py +8 -4
  17. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/presto.py +1 -1
  18. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/python.py +10 -2
  19. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/snowflake.py +4 -35
  20. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/sqlite.py +2 -0
  21. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/teradata.py +2 -3
  22. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/trino.py +1 -1
  23. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/tsql.py +11 -1
  24. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/annotate_types.py +29 -15
  25. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/normalize_identifiers.py +33 -11
  26. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/qualify_columns.py +136 -12
  27. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/qualify_tables.py +10 -0
  28. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/resolver.py +2 -3
  29. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/scope.py +8 -4
  30. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parser.py +118 -53
  31. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/bigquery.py +2 -1
  32. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/clickhouse.py +27 -6
  33. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/dremio.py +6 -0
  34. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/mysql.py +7 -0
  35. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/oracle.py +1 -0
  36. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/postgres.py +7 -2
  37. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/snowflake.py +11 -35
  38. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/spark2.py +16 -2
  39. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/starrocks.py +3 -0
  40. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/teradata.py +1 -1
  41. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/tsql.py +3 -1
  42. {sqlglotc-30.17.0 → sqlglotc-30.18.0/sqlglotc.egg-info}/PKG-INFO +3 -3
  43. sqlglotc-30.18.0/sqlglotc.egg-info/requires.txt +6 -0
  44. sqlglotc-30.18.0/sqlglotc.egg-info/scm_version.json +8 -0
  45. sqlglotc-30.17.0/sqlglotc.egg-info/requires.txt +0 -6
  46. sqlglotc-30.17.0/sqlglotc.egg-info/scm_version.json +0 -8
  47. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/MANIFEST.in +0 -0
  48. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/setup.cfg +0 -0
  49. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/setup.py +0 -0
  50. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/anonymize.py +0 -0
  51. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/errors.py +0 -0
  52. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/aggregate.py +0 -0
  53. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/array.py +0 -0
  54. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/core.py +0 -0
  55. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/datatypes.py +0 -0
  56. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/dml.py +0 -0
  57. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/functions.py +0 -0
  58. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/math.py +0 -0
  59. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/properties.py +0 -0
  60. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/expressions/temporal.py +0 -0
  61. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/athena.py +0 -0
  62. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/bigquery.py +0 -0
  63. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/databricks.py +0 -0
  64. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/dax.py +0 -0
  65. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/doris.py +0 -0
  66. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/drill.py +0 -0
  67. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/druid.py +0 -0
  68. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/dune.py +0 -0
  69. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/exasol.py +0 -0
  70. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/fabric.py +0 -0
  71. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/materialize.py +0 -0
  72. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/mysql.py +0 -0
  73. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/prql.py +0 -0
  74. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/redshift.py +0 -0
  75. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/risingwave.py +0 -0
  76. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/singlestore.py +0 -0
  77. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/solr.py +0 -0
  78. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/spark.py +0 -0
  79. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/spark2.py +0 -0
  80. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/starrocks.py +0 -0
  81. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/generators/tableau.py +0 -0
  82. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/helper.py +0 -0
  83. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/lineage.py +0 -0
  84. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  85. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  86. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/qualify.py +0 -0
  87. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/optimizer/simplify.py +0 -0
  88. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/athena.py +0 -0
  89. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/base.py +0 -0
  90. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/databricks.py +0 -0
  91. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/dax.py +0 -0
  92. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/doris.py +0 -0
  93. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/drill.py +0 -0
  94. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/druid.py +0 -0
  95. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/duckdb.py +0 -0
  96. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/dune.py +0 -0
  97. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/exasol.py +0 -0
  98. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/fabric.py +0 -0
  99. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/hive.py +0 -0
  100. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/materialize.py +0 -0
  101. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/presto.py +0 -0
  102. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/prql.py +0 -0
  103. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/redshift.py +0 -0
  104. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/risingwave.py +0 -0
  105. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/singlestore.py +0 -0
  106. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/solr.py +0 -0
  107. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/spark.py +0 -0
  108. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/sqlite.py +0 -0
  109. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/tableau.py +0 -0
  110. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/parsers/trino.py +0 -0
  111. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/schema.py +0 -0
  112. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/serde.py +0 -0
  113. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/time.py +0 -0
  114. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/tokenizer_core.py +0 -0
  115. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglot/trie.py +0 -0
  116. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  117. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  118. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  119. {sqlglotc-30.17.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/top_level.txt +0 -0
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.17.0
3
+ Version: 30.18.0
4
4
  Summary: mypyc-compiled extensions for sqlglot
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://sqlglot.com/
8
8
  Project-URL: Repository, https://github.com/tobymao/sqlglot
9
9
  Requires-Python: >=3.10
10
- Requires-Dist: sqlglot==30.17.0
10
+ Requires-Dist: sqlglot==30.18.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
14
- Requires-Dist: sqlglot-mypy>=2.3.0.post1; extra == "dev"
14
+ Requires-Dist: sqlglot-mypy>=2.3.0.post2; extra == "dev"
15
15
  Dynamic: requires-dist
@@ -7,7 +7,7 @@ license = "MIT"
7
7
  requires-python = ">= 3.10"
8
8
 
9
9
  [project.optional-dependencies]
10
- dev = ["setuptools >= 61.0", "setuptools_scm", "sqlglot-mypy >= 2.3.0.post1"]
10
+ dev = ["setuptools >= 61.0", "setuptools_scm", "sqlglot-mypy >= 2.3.0.post2"]
11
11
 
12
12
  [project.urls]
13
13
  Homepage = "https://sqlglot.com/"
@@ -17,7 +17,7 @@ Repository = "https://github.com/tobymao/sqlglot"
17
17
  requires = [
18
18
  "setuptools >= 61.0",
19
19
  "setuptools_scm",
20
- "sqlglot-mypy >= 2.3.0.post1",
20
+ "sqlglot-mypy >= 2.3.0.post2",
21
21
  "types-python-dateutil",
22
22
  "sqlglot",
23
23
  ]
@@ -109,7 +109,7 @@ class RowReader:
109
109
  if columns is not None
110
110
  else {}
111
111
  )
112
- self.row = None
112
+ self.row = ()
113
113
 
114
114
  def __getitem__(self, column):
115
115
  return self.row[self.columns[column]]
@@ -495,11 +495,16 @@ def cast(
495
495
  target_dialect = Dialect.get_or_raise(dialect)
496
496
  type_mapping = target_dialect.generator_class.TYPE_MAPPING
497
497
 
498
- existing_cast_type: DType = expr.to.this
498
+ existing_cast_type = expr.to.this
499
499
  new_cast_type: DType = data_type.this
500
- types_are_equivalent = type_mapping.get(
501
- existing_cast_type, existing_cast_type.value
502
- ) == type_mapping.get(new_cast_type, new_cast_type.value)
500
+ # `this` is only a plain type enum for simple types; complex ones such as
501
+ # INTERVAL nest another expression there, so the equivalence check is skipped.
502
+ types_are_equivalent = (
503
+ isinstance(existing_cast_type, DType)
504
+ and isinstance(new_cast_type, DType)
505
+ and type_mapping.get(existing_cast_type, existing_cast_type.value)
506
+ == type_mapping.get(new_cast_type, new_cast_type.value)
507
+ )
503
508
 
504
509
  if expr.is_type(data_type) or types_are_equivalent:
505
510
  return expr
@@ -41,6 +41,10 @@ class ZeroFillColumnConstraint(ColumnConstraint):
41
41
  arg_types = {}
42
42
 
43
43
 
44
+ class BinaryColumnConstraint(ColumnConstraint):
45
+ arg_types = {}
46
+
47
+
44
48
  class PeriodForSystemTimeConstraint(Expression, ColumnConstraintKind):
45
49
  arg_types = {"this": True, "expression": True}
46
50
 
@@ -345,8 +345,8 @@ class MergeTreeTTL(Expression):
345
345
 
346
346
  class Drop(Expression):
347
347
  arg_types = {
348
- "this": False,
349
348
  "kind": False,
349
+ "tables": False,
350
350
  "expressions": False,
351
351
  "exists": False,
352
352
  "temporary": False,
@@ -54,6 +54,10 @@ class JSONBContainsAllTopKeys(Expression, Binary, Predicate, Func):
54
54
  pass
55
55
 
56
56
 
57
+ class JSONBContainsTopKey(Expression, Binary, Predicate, Func):
58
+ pass
59
+
60
+
57
61
  class JSONBContainsAnyTopKeys(Expression, Binary, Predicate, Func):
58
62
  pass
59
63
 
@@ -624,6 +624,7 @@ class Group(Expression):
624
624
  arg_types = {
625
625
  "expressions": False,
626
626
  "grouping_sets": False,
627
+ "grouping_sets_as_group_by_element": False,
627
628
  "cube": False,
628
629
  "rollup": False,
629
630
  "totals": False,
@@ -1891,7 +1892,7 @@ class Where(Expression):
1891
1892
  class Analyze(Expression):
1892
1893
  arg_types = {
1893
1894
  "kind": False,
1894
- "this": False,
1895
+ "tables": False,
1895
1896
  "options": False,
1896
1897
  "mode": False,
1897
1898
  "partition": False,
@@ -478,7 +478,8 @@ class RegexpReplace(Expression, Func):
478
478
 
479
479
 
480
480
  class RegexpSplit(Expression, Func):
481
- arg_types = {"this": True, "expression": True, "limit": False}
481
+ # "mode" is Dremio-specific, appended after "limit" for from_arg_list compat
482
+ arg_types = {"this": True, "expression": True, "limit": False, "mode": False}
482
483
 
483
484
 
484
485
  class RegexpSubstr(Expression, Func):
@@ -147,6 +147,7 @@ class Generator:
147
147
  exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
148
148
  exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
149
149
  exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
150
+ exp.BinaryColumnConstraint: lambda *_: "BINARY",
150
151
  exp.CaseSpecificColumnConstraint: lambda _, e: (
151
152
  f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
152
153
  ),
@@ -206,6 +207,7 @@ class Generator:
206
207
  exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
207
208
  exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
208
209
  exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
210
+ exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
209
211
  exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
210
212
  exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
211
213
  exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
@@ -351,6 +353,9 @@ class Generator:
351
353
  # The separator for grouping sets and rollups
352
354
  GROUPINGS_SEP = ","
353
355
 
356
+ # Whether GROUPING SETS can follow GROUP BY expressions without a comma
357
+ SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
358
+
354
359
  # The string used for creating an index on a table
355
360
  INDEX_ON = "ON"
356
361
 
@@ -850,6 +855,16 @@ class Generator:
850
855
 
851
856
  RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
852
857
 
858
+ MOD_OPERATOR = "%"
859
+
860
+ # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
861
+ MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
862
+ exp.Mul,
863
+ exp.Div,
864
+ exp.IntDiv,
865
+ exp.Mod,
866
+ )
867
+
853
868
  SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
854
869
 
855
870
  SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
@@ -1826,7 +1841,7 @@ class Generator:
1826
1841
  return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1827
1842
 
1828
1843
  def drop_sql(self, expression: exp.Drop) -> str:
1829
- this = self.sql(expression, "this")
1844
+ tables = self.expressions(expression, key="tables", flat=True)
1830
1845
  expressions = self.expressions(expression, flat=True)
1831
1846
  expressions = f" ({expressions})" if expressions else ""
1832
1847
  kind = expression.args["kind"]
@@ -1848,7 +1863,7 @@ class Generator:
1848
1863
  purge = " PURGE" if expression.args.get("purge") else ""
1849
1864
  sync = " SYNC" if expression.args.get("sync") else ""
1850
1865
  force = " FORCE" if expression.args.get("force") else ""
1851
- return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1866
+ return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1852
1867
 
1853
1868
  def set_operation(self, expression: exp.SetOperation) -> str:
1854
1869
  op_type = type(expression)
@@ -2819,7 +2834,18 @@ class Generator:
2819
2834
  and groupings
2820
2835
  and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2821
2836
  ):
2822
- group_by = f"{group_by}{self.GROUPINGS_SEP}"
2837
+ add_separator = True
2838
+
2839
+ if grouping_sets and not expression.args.get("grouping_sets_as_group_by_element"):
2840
+ if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2841
+ add_separator = False
2842
+ else:
2843
+ self.unsupported(
2844
+ "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2845
+ )
2846
+
2847
+ if add_separator:
2848
+ group_by = f"{group_by}{self.GROUPINGS_SEP}"
2823
2849
 
2824
2850
  return f"{group_by}{groupings}"
2825
2851
 
@@ -4587,7 +4613,15 @@ class Generator:
4587
4613
  return self.binary(expression, "<=")
4588
4614
 
4589
4615
  def mod_sql(self, expression: exp.Mod) -> str:
4590
- return self.binary(expression, "%")
4616
+ this = self.sql(expression, "this")
4617
+ expr = self.sql(expression, "expression")
4618
+ sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4619
+
4620
+ parent = expression.parent
4621
+ if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4622
+ return f"({sql})"
4623
+
4624
+ return sql
4591
4625
 
4592
4626
  def mul_sql(self, expression: exp.Mul) -> str:
4593
4627
  return self.binary(expression, "*")
@@ -5035,6 +5069,12 @@ class Generator:
5035
5069
 
5036
5070
  return self.sql(case)
5037
5071
 
5072
+ def nthvalue_sql(self, expression: exp.NthValue) -> str:
5073
+ if expression.args.get("from_first") is False:
5074
+ self.unsupported("NTH_VALUE FROM LAST is not supported")
5075
+
5076
+ return self.function_fallback_sql(expression)
5077
+
5038
5078
  def comprehension_sql(self, expression: exp.Comprehension) -> str:
5039
5079
  this = self.sql(expression, "this")
5040
5080
  expr = self.sql(expression, "expression")
@@ -5395,9 +5435,11 @@ class Generator:
5395
5435
 
5396
5436
  if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5397
5437
  if self.IGNORE_NULLS_BEFORE_ORDER:
5438
+ from sqlglot.optimizer.scope import find_all_in_scope
5439
+
5398
5440
  # The first modifier here will be the one closest to the AggFunc's arg
5399
5441
  mods = sorted(
5400
- expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5442
+ find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5401
5443
  key=lambda x: (
5402
5444
  0
5403
5445
  if isinstance(x, exp.HavingMax)
@@ -6037,8 +6079,8 @@ class Generator:
6037
6079
  options = f" {options}" if options else ""
6038
6080
  kind = self.sql(expression, "kind")
6039
6081
  kind = f" {kind}" if kind else ""
6040
- this = self.sql(expression, "this")
6041
- this = f" {this}" if this else ""
6082
+ tables = self.expressions(expression, key="tables", flat=True)
6083
+ tables = f" {tables}" if tables else ""
6042
6084
  mode = self.sql(expression, "mode")
6043
6085
  mode = f" {mode}" if mode else ""
6044
6086
  properties = self.sql(expression, "properties")
@@ -6047,7 +6089,7 @@ class Generator:
6047
6089
  partition = f" {partition}" if partition else ""
6048
6090
  inner_expression = self.sql(expression, "expression")
6049
6091
  inner_expression = f" {inner_expression}" if inner_expression else ""
6050
- return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
6092
+ return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6051
6093
 
6052
6094
  def xmltable_sql(self, expression: exp.XMLTable) -> str:
6053
6095
  this = self.sql(expression, "this")
@@ -372,12 +372,8 @@ class ClickHouseGenerator(generator.Generator):
372
372
  exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),
373
373
  exp.Stddev: rename_func("stddevSamp"),
374
374
  exp.Chr: rename_func("CHAR"),
375
- exp.Lag: lambda self, e: self.func(
376
- "lagInFrame", e.this, e.args.get("offset"), e.args.get("default")
377
- ),
378
- exp.Lead: lambda self, e: self.func(
379
- "leadInFrame", e.this, e.args.get("offset"), e.args.get("default")
380
- ),
375
+ exp.Lag: rename_func("lag"),
376
+ exp.Lead: rename_func("lead"),
381
377
  exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
382
378
  rename_func("editDistance")
383
379
  ),
@@ -70,6 +70,9 @@ class DremioGenerator(generator.Generator):
70
70
  exp.DateAdd: _date_delta_sql("DATE_ADD"),
71
71
  exp.DateSub: _date_delta_sql("DATE_SUB"),
72
72
  exp.GenerateSeries: rename_func("ARRAY_GENERATE_RANGE"),
73
+ exp.RegexpSplit: lambda self, e: self.func(
74
+ "REGEXP_SPLIT", e.this, e.expression, e.args.get("mode"), e.args.get("limit")
75
+ ),
73
76
  }
74
77
 
75
78
  def version_sql(self, expression: exp.Version) -> str:
@@ -10,10 +10,10 @@ from sqlglot.dialects.dialect import (
10
10
  DATETIME_DELTA,
11
11
  JSON_EXTRACT_TYPE,
12
12
  approx_count_distinct_sql,
13
+ arrow_json_extract_sql,
13
14
  array_append_sql,
14
15
  array_compact_sql,
15
16
  array_concat_sql,
16
- arrow_json_extract_sql,
17
17
  count_if_to_sum,
18
18
  date_delta_to_binary_interval_op,
19
19
  datestrtodate_sql,
@@ -2469,13 +2469,6 @@ class DuckDBGenerator(generator.Generator):
2469
2469
 
2470
2470
  return self.sql(result)
2471
2471
 
2472
- def nthvalue_sql(self, expression: exp.NthValue) -> str:
2473
- from_first = expression.args.get("from_first", True)
2474
- if not from_first:
2475
- self.unsupported("DuckDB's NTH_VALUE doesn't support starting from the end ")
2476
-
2477
- return self.function_fallback_sql(expression)
2478
-
2479
2472
  def randstr_sql(self, expression: exp.Randstr) -> str:
2480
2473
  """
2481
2474
  Transpile Snowflake's RANDSTR to DuckDB equivalent using deterministic hash-based random.
@@ -4720,9 +4713,14 @@ class DuckDBGenerator(generator.Generator):
4720
4713
 
4721
4714
  def jsonextractscalar_sql(self, expression: exp.JSONExtractScalar) -> str:
4722
4715
  if expression.args.get("scalar_only"):
4723
- expression = exp.JSONExtractScalar(
4716
+ json_value = exp.JSONExtractScalar(
4724
4717
  this=rename_func("JSON_VALUE")(self, expression), expression="'$'"
4725
4718
  )
4719
+
4720
+ # `->>` binds looser than most operators, so the wrap logic needs the parent
4721
+ json_value.parent = expression.parent
4722
+ expression = json_value
4723
+
4726
4724
  return _arrow_json_extract_sql(self, expression)
4727
4725
 
4728
4726
  def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
@@ -223,6 +223,7 @@ class HiveGenerator(generator.Generator):
223
223
  SELECT_KINDS: tuple[str, ...] = ()
224
224
  TRY_SUPPORTED = False
225
225
  SUPPORTS_UESCAPE = False
226
+ SUPPORTS_GROUPING_SETS_AS_SUFFIX = True
226
227
  SUPPORTS_DECODE_CASE = False
227
228
  LIMIT_FETCH = "LIMIT"
228
229
  TABLESAMPLE_WITH_METHOD = False
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
-
4
3
  from sqlglot import exp, generator, transforms
5
4
  from sqlglot.dialects.dialect import (
6
5
  groupconcat_sql,
7
6
  no_ilike_sql,
7
+ nth_value_from_sql,
8
8
  rename_func,
9
9
  strposition_sql,
10
10
  trim_sql,
@@ -77,6 +77,7 @@ class OracleGenerator(generator.Generator):
77
77
  exp.LogicalOr: rename_func("MAX"),
78
78
  exp.LogicalAnd: rename_func("MIN"),
79
79
  exp.Mod: rename_func("MOD"),
80
+ exp.NthValue: nth_value_from_sql,
80
81
  exp.Rand: rename_func("DBMS_RANDOM.VALUE"),
81
82
  exp.Select: transforms.preprocess(
82
83
  [
@@ -7,6 +7,7 @@ from sqlglot.dialects.dialect import (
7
7
  DATE_ADD_OR_SUB,
8
8
  JSON_EXTRACT_TYPE,
9
9
  any_value_to_max_sql,
10
+ arrow_json_extract_sql,
10
11
  array_append_sql,
11
12
  array_concat_sql,
12
13
  bool_xor_sql,
@@ -189,7 +190,11 @@ def _json_extract_sql(
189
190
  if not isinstance(path, (exp.JSONPath, exp.Variadic)) and not ensure_list(
190
191
  expression.args.get("expressions")
191
192
  ):
192
- return self.binary(expression, op)
193
+ return arrow_json_extract_sql(self, expression, op=op)
194
+
195
+ # JSON_EXTRACT_PATH requires a key, so use an empty variadic array for the root path
196
+ if len(path.expressions) == 1 and isinstance(path.expressions[0], exp.JSONPathRoot):
197
+ expression.set("expression", exp.Variadic(this=exp.Literal.string("{}")))
193
198
 
194
199
  if expression.args.get("only_json_types"):
195
200
  return json_extract_segments(name, quoted_index=False, op=op)(self, expression)
@@ -347,9 +352,8 @@ class PostgresGenerator(generator.Generator):
347
352
  ),
348
353
  exp.JSONExtract: _json_extract_sql("JSON_EXTRACT_PATH", "->"),
349
354
  exp.JSONExtractScalar: _json_extract_sql("JSON_EXTRACT_PATH_TEXT", "->>"),
350
- exp.JSONBExtract: lambda self, e: self.binary(e, "#>"),
351
- exp.JSONBExtractScalar: lambda self, e: self.binary(e, "#>>"),
352
- exp.JSONBContains: lambda self, e: self.binary(e, "?"),
355
+ exp.JSONBExtract: lambda self, e: arrow_json_extract_sql(self, e, op="#>"),
356
+ exp.JSONBExtractScalar: lambda self, e: arrow_json_extract_sql(self, e, op="#>>"),
353
357
  exp.ParseJSON: lambda self, e: self.sql(exp.cast(e.this, exp.DType.JSON)),
354
358
  exp.JSONPathKey: json_path_key_only_name,
355
359
  exp.JSONPathRoot: lambda *_: "",
@@ -383,7 +383,7 @@ class PrestoGenerator(generator.Generator):
383
383
  transforms.eliminate_window_clause,
384
384
  transforms.eliminate_qualify,
385
385
  transforms.eliminate_distinct_on,
386
- transforms.explode_projection_to_unnest(1),
386
+ transforms.explode_projection_to_unnest(1, unnest_map=True),
387
387
  transforms.eliminate_semi_and_anti_joins,
388
388
  amend_exploded_column_table,
389
389
  ]
@@ -43,7 +43,7 @@ def _case_sql(self, expression):
43
43
  condition = f"{this} = ({condition})" if this else condition
44
44
  chain = f"{true} if {condition} else ({chain})"
45
45
 
46
- return chain
46
+ return f"({chain})"
47
47
 
48
48
 
49
49
  def _lambda_sql(self, e: exp.Lambda) -> str:
@@ -78,11 +78,18 @@ def _div_sql(self: generator.Generator, e: exp.Div) -> str:
78
78
  if e.args.get("typed") and not (
79
79
  e.this.is_type(*exp.DataType.REAL_TYPES) or e.expression.is_type(*exp.DataType.REAL_TYPES)
80
80
  ):
81
- sql = f"int({sql})"
81
+ sql = f"INT({sql})"
82
82
 
83
83
  return sql
84
84
 
85
85
 
86
+ def _dpipe_sql(self: generator.Generator, e: exp.DPipe) -> str:
87
+ if e.this.is_type(exp.DataType.Type.ARRAY) or e.expression.is_type(exp.DataType.Type.ARRAY):
88
+ return self.func("ARRAYCONCAT", e.this, e.expression)
89
+
90
+ return self.func("SAFECONCAT" if e.args.get("safe") else "CONCAT", e.this, e.expression)
91
+
92
+
86
93
  class PythonGenerator(generator.Generator):
87
94
  TRANSFORMS = {
88
95
  **{klass: _rename for klass in subclasses(exp.__name__, exp.Binary)},
@@ -100,6 +107,7 @@ class PythonGenerator(generator.Generator):
100
107
  ),
101
108
  exp.Distinct: lambda self, e: f"set({self.sql(e, 'this')})",
102
109
  exp.Div: _div_sql,
110
+ exp.DPipe: _dpipe_sql,
103
111
  exp.Extract: lambda self, e: f"EXTRACT('{e.name.lower()}', {self.sql(e, 'expression')})",
104
112
  exp.ILike: _like_sql,
105
113
  exp.In: lambda self, e: self.func("IN", e.this, *e.expressions),
@@ -17,6 +17,7 @@ from sqlglot.dialects.dialect import (
17
17
  min_or_least,
18
18
  no_make_interval_sql,
19
19
  no_timestamp_sql,
20
+ nth_value_from_sql,
20
21
  rename_func,
21
22
  strposition_sql,
22
23
  timestampdiff_sql,
@@ -96,25 +97,6 @@ def _unqualify_pivot_columns(expression: exp.Expr) -> exp.Expr:
96
97
  return expression
97
98
 
98
99
 
99
- def _flatten_structured_types_unless_iceberg(expression: exp.Expr) -> exp.Expr:
100
- assert isinstance(expression, exp.Create)
101
-
102
- def _flatten_structured_type(expression: exp.Expr) -> exp.Expr:
103
- if isinstance(expression, exp.DataType) and expression.this in exp.DataType.NESTED_TYPES:
104
- expression.set("expressions", None)
105
- return expression
106
-
107
- props = expression.args.get("properties")
108
- if isinstance(expression.this, exp.Schema) and not (props and props.find(exp.IcebergProperty)):
109
- for schema_expression in expression.this.expressions:
110
- if isinstance(schema_expression, exp.ColumnDef):
111
- column_type = schema_expression.kind
112
- if isinstance(column_type, exp.DataType):
113
- column_type.transform(_flatten_structured_type, copy=False)
114
-
115
- return expression
116
-
117
-
118
100
  def _unnest_generate_date_array(unnest: exp.Unnest) -> None:
119
101
  generate_date_array = unnest.expressions[0]
120
102
  start = generate_date_array.args.get("start")
@@ -437,7 +419,6 @@ class SnowflakeGenerator(generator.Generator):
437
419
  exp.BitwiseNot: rename_func("BITNOT"),
438
420
  exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"),
439
421
  exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"),
440
- exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]),
441
422
  exp.CurrentTimestamp: lambda self, e: (
442
423
  self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e)
443
424
  ),
@@ -518,6 +499,7 @@ class SnowflakeGenerator(generator.Generator):
518
499
  exp.MakeInterval: no_make_interval_sql,
519
500
  exp.Max: max_or_greatest,
520
501
  exp.Min: min_or_least,
502
+ exp.NthValue: nth_value_from_sql,
521
503
  exp.ParseJSON: lambda self, e: self.func(
522
504
  f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this
523
505
  ),
@@ -629,19 +611,6 @@ class SnowflakeGenerator(generator.Generator):
629
611
  nulls_first = None
630
612
  return self.func("ARRAY_SORT", expression.this, asc, nulls_first)
631
613
 
632
- def nthvalue_sql(self, expression: exp.NthValue) -> str:
633
- result = self.func("NTH_VALUE", expression.this, expression.args.get("offset"))
634
-
635
- from_first = expression.args.get("from_first")
636
-
637
- if from_first is not None:
638
- if from_first:
639
- result = result + " FROM FIRST"
640
- else:
641
- result = result + " FROM LAST"
642
-
643
- return result
644
-
645
614
  SUPPORTED_JSON_PATH_PARTS = {
646
615
  exp.JSONPathKey,
647
616
  exp.JSONPathRoot,
@@ -1179,7 +1148,7 @@ class SnowflakeGenerator(generator.Generator):
1179
1148
  # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an
1180
1149
  # equivalent conditional aggregation, i.e. wrap the input values in an IFF
1181
1150
  agg = expression.this
1182
- agg_arg = agg.this
1151
+ agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this
1183
1152
  cond = expression.expression.this
1184
1153
 
1185
1154
  if isinstance(agg, exp.WithinGroup):
@@ -1203,7 +1172,7 @@ class SnowflakeGenerator(generator.Generator):
1203
1172
  # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF
1204
1173
  # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of
1205
1174
  # them, which Snowflake rejects. Use its native COUNT_IF instead.
1206
- if isinstance(agg, exp.Count) and agg_arg.is_star:
1175
+ if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star:
1207
1176
  return self.func("COUNT_IF", cond)
1208
1177
 
1209
1178
  # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the
@@ -173,6 +173,7 @@ class SQLiteGenerator(generator.Generator):
173
173
  exp.LogicalAnd: rename_func("MIN"),
174
174
  exp.Pivot: no_pivot_sql,
175
175
  exp.Rand: rename_func("RANDOM"),
176
+ exp.RegexpLike: lambda self, e: self.binary(e, "REGEXP"),
176
177
  exp.Select: transforms.preprocess(
177
178
  [
178
179
  _offset_to_limit,
@@ -372,6 +373,7 @@ class SQLiteGenerator(generator.Generator):
372
373
  if (
373
374
  expression.text("kind").upper() == "RANGE"
374
375
  and expression.text("start").upper() == "CURRENT ROW"
376
+ and expression.args.get("end") is None
375
377
  ):
376
378
  return "RANGE CURRENT ROW"
377
379
 
@@ -43,6 +43,8 @@ class TeradataGenerator(generator.Generator):
43
43
  SUPPORTS_DECODE_CASE = False
44
44
 
45
45
  AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
46
+ MOD_OPERATOR = "MOD"
47
+ MOD_PAREN_PARENT_TYPES = (*generator.Generator.MOD_PAREN_PARENT_TYPES, exp.Pow)
46
48
 
47
49
  LIMIT_IS_TOP = True
48
50
  JOIN_HINTS = False
@@ -127,9 +129,6 @@ class TeradataGenerator(generator.Generator):
127
129
  sql = f"UPDATE {this}{from_sql} SET {set_sql}{where_sql}"
128
130
  return self.prepend_ctes(expression, sql)
129
131
 
130
- def mod_sql(self, expression: exp.Mod) -> str:
131
- return self.binary(expression, "MOD")
132
-
133
132
  def rangen_sql(self, expression: exp.RangeN) -> str:
134
133
  this = self.sql(expression, "this")
135
134
  expressions_sql = self.expressions(expression)
@@ -34,7 +34,7 @@ class TrinoGenerator(PrestoGenerator):
34
34
  [
35
35
  transforms.eliminate_qualify,
36
36
  transforms.eliminate_distinct_on,
37
- transforms.explode_projection_to_unnest(1),
37
+ transforms.explode_projection_to_unnest(1, unnest_map=True),
38
38
  transforms.eliminate_semi_and_anti_joins,
39
39
  amend_exploded_column_table,
40
40
  ]
@@ -103,7 +103,16 @@ def qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr:
103
103
  def _json_extract_sql(
104
104
  self: TSQLGenerator, expression: exp.JSONExtract | exp.JSONExtractScalar
105
105
  ) -> str:
106
+ # JSON_QUERY returns objects and arrays, JSON_VALUE returns scalars. A scalar-only
107
+ # extraction maps to JSON_VALUE; a source that also returns non-scalar values as
108
+ # text (e.g. SQLite's ->>) needs to try both, like a generic JSONExtract does
109
+ if isinstance(expression, exp.JSONExtractScalar) and expression.args.get("scalar_only"):
110
+ return self.func("JSON_VALUE", expression.this, expression.expression)
111
+
106
112
  json_query = self.func("JSON_QUERY", expression.this, expression.expression)
113
+ if expression.args.get("json_query"):
114
+ return json_query
115
+
107
116
  json_value = self.func("JSON_VALUE", expression.this, expression.expression)
108
117
  return self.func("ISNULL", json_query, json_value)
109
118
 
@@ -596,7 +605,8 @@ class TSQLGenerator(generator.Generator):
596
605
 
597
606
  def drop_sql(self, expression: exp.Drop) -> str:
598
607
  if expression.args["kind"] == "VIEW":
599
- expression.this.set("catalog", None)
608
+ for table in expression.args.get("tables") or []:
609
+ table.set("catalog", None)
600
610
  return super().drop_sql(expression)
601
611
 
602
612
  def options_modifier(self, expression: exp.Expr) -> str: