sqlglotc 30.16.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.16.0/sqlglotc.egg-info → sqlglotc-30.18.0}/PKG-INFO +3 -3
  2. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/pyproject.toml +2 -2
  3. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/executor/table.py +1 -1
  4. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/builders.py +9 -4
  5. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/constraints.py +4 -0
  6. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/core.py +10 -1
  7. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/ddl.py +1 -1
  8. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/json.py +4 -0
  9. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/query.py +24 -2
  10. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/string.py +2 -1
  11. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generator.py +70 -8
  12. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/clickhouse.py +2 -6
  13. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/dremio.py +3 -0
  14. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/duckdb.py +7 -9
  15. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/hive.py +1 -0
  16. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/oracle.py +2 -1
  17. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/postgres.py +8 -4
  18. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/presto.py +1 -1
  19. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/python.py +24 -3
  20. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/snowflake.py +4 -35
  21. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/sqlite.py +18 -1
  22. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/teradata.py +2 -3
  23. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/trino.py +27 -1
  24. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/tsql.py +11 -1
  25. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/annotate_types.py +43 -14
  26. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/canonicalize_internal_names.py +6 -4
  27. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/normalize_identifiers.py +33 -11
  28. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/qualify_columns.py +163 -27
  29. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/qualify_tables.py +24 -1
  30. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/resolver.py +2 -3
  31. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/scope.py +66 -9
  32. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/simplify.py +17 -4
  33. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parser.py +144 -59
  34. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/bigquery.py +2 -1
  35. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/clickhouse.py +29 -8
  36. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/dremio.py +6 -0
  37. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/mysql.py +7 -0
  38. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/oracle.py +1 -0
  39. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/postgres.py +7 -2
  40. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/prql.py +1 -1
  41. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/snowflake.py +11 -35
  42. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/spark2.py +20 -1
  43. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/sqlite.py +27 -3
  44. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/starrocks.py +3 -0
  45. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/teradata.py +1 -1
  46. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/trino.py +45 -0
  47. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/tsql.py +3 -1
  48. {sqlglotc-30.16.0 → sqlglotc-30.18.0/sqlglotc.egg-info}/PKG-INFO +3 -3
  49. sqlglotc-30.18.0/sqlglotc.egg-info/requires.txt +6 -0
  50. sqlglotc-30.18.0/sqlglotc.egg-info/scm_version.json +8 -0
  51. sqlglotc-30.16.0/sqlglotc.egg-info/requires.txt +0 -6
  52. sqlglotc-30.16.0/sqlglotc.egg-info/scm_version.json +0 -8
  53. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/MANIFEST.in +0 -0
  54. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/setup.cfg +0 -0
  55. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/setup.py +0 -0
  56. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/anonymize.py +0 -0
  57. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/errors.py +0 -0
  58. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/aggregate.py +0 -0
  59. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/array.py +0 -0
  60. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/datatypes.py +0 -0
  61. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/dml.py +0 -0
  62. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/functions.py +0 -0
  63. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/math.py +0 -0
  64. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/properties.py +0 -0
  65. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/expressions/temporal.py +0 -0
  66. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/athena.py +0 -0
  67. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/bigquery.py +0 -0
  68. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/databricks.py +0 -0
  69. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/dax.py +0 -0
  70. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/doris.py +0 -0
  71. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/drill.py +0 -0
  72. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/druid.py +0 -0
  73. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/dune.py +0 -0
  74. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/exasol.py +0 -0
  75. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/fabric.py +0 -0
  76. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/materialize.py +0 -0
  77. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/mysql.py +0 -0
  78. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/prql.py +0 -0
  79. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/redshift.py +0 -0
  80. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/risingwave.py +0 -0
  81. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/singlestore.py +0 -0
  82. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/solr.py +0 -0
  83. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/spark.py +0 -0
  84. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/spark2.py +0 -0
  85. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/starrocks.py +0 -0
  86. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/generators/tableau.py +0 -0
  87. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/helper.py +0 -0
  88. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/lineage.py +0 -0
  89. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  90. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/optimizer/qualify.py +0 -0
  91. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/athena.py +0 -0
  92. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/base.py +0 -0
  93. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/databricks.py +0 -0
  94. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/dax.py +0 -0
  95. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/doris.py +0 -0
  96. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/drill.py +0 -0
  97. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/druid.py +0 -0
  98. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/duckdb.py +0 -0
  99. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/dune.py +0 -0
  100. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/exasol.py +0 -0
  101. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/fabric.py +0 -0
  102. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/hive.py +0 -0
  103. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/materialize.py +0 -0
  104. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/presto.py +0 -0
  105. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/redshift.py +0 -0
  106. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/risingwave.py +0 -0
  107. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/singlestore.py +0 -0
  108. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/solr.py +0 -0
  109. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/spark.py +0 -0
  110. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/parsers/tableau.py +0 -0
  111. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/schema.py +0 -0
  112. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/serde.py +0 -0
  113. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/time.py +0 -0
  114. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/tokenizer_core.py +0 -0
  115. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglot/trie.py +0 -0
  116. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  117. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  118. {sqlglotc-30.16.0 → sqlglotc-30.18.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  119. {sqlglotc-30.16.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.16.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.16.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.1.0.post9; 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.1.0.post9"]
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.1.0.post9",
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
 
@@ -1699,7 +1699,16 @@ class AggFunc(Func):
1699
1699
 
1700
1700
 
1701
1701
  class Column(Expression, Condition):
1702
- arg_types = {"this": True, "table": False, "db": False, "catalog": False, "join_mark": False}
1702
+ # "shadow" marks a column whose qualifier is shadowed by a projection alias, so it must be
1703
+ # rendered unqualified in dialects where PROJECTION_ALIASES_SHADOW_SOURCE_NAMES is set
1704
+ arg_types = {
1705
+ "this": True,
1706
+ "table": False,
1707
+ "db": False,
1708
+ "catalog": False,
1709
+ "join_mark": False,
1710
+ "shadow": False,
1711
+ }
1703
1712
 
1704
1713
  @property
1705
1714
  def table(self) -> str:
@@ -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,
@@ -1050,6 +1051,11 @@ class SetOperation(Expression, Query):
1050
1051
  def named_selects(self) -> list[str]:
1051
1052
  expr: Expr = self
1052
1053
  while isinstance(expr, SetOperation):
1054
+ if expr.args.get("by_name"):
1055
+ left = t.cast(Selectable, expr.this.unnest()).named_selects
1056
+ right = t.cast(Selectable, expr.expression.unnest()).named_selects
1057
+ return list(dict.fromkeys(left + right))
1058
+
1053
1059
  expr = expr.this.unnest()
1054
1060
  return _named_selects(expr)
1055
1061
 
@@ -1886,7 +1892,7 @@ class Where(Expression):
1886
1892
  class Analyze(Expression):
1887
1893
  arg_types = {
1888
1894
  "kind": False,
1889
- "this": False,
1895
+ "tables": False,
1890
1896
  "options": False,
1891
1897
  "mode": False,
1892
1898
  "partition": False,
@@ -2124,7 +2130,23 @@ class CaseStatement(Expression):
2124
2130
 
2125
2131
 
2126
2132
  class WhileBlock(Expression):
2127
- arg_types = {"this": True, "body": True}
2133
+ arg_types = {"this": True, "body": True, "label": False}
2134
+
2135
+
2136
+ class LoopBlock(Expression):
2137
+ arg_types = {"body": True, "label": False}
2138
+
2139
+
2140
+ class RepeatBlock(Expression):
2141
+ arg_types = {"body": True, "until": True, "label": False}
2142
+
2143
+
2144
+ class Leave(Expression):
2145
+ pass
2146
+
2147
+
2148
+ class Iterate(Expression):
2149
+ pass
2128
2150
 
2129
2151
 
2130
2152
  class EndStatement(Expression):
@@ -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__"
@@ -1151,6 +1166,10 @@ class Generator:
1151
1166
  return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1152
1167
 
1153
1168
  def column_parts(self, expression: exp.Column) -> str:
1169
+ if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1170
+ # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1171
+ return self.sql(expression, "this")
1172
+
1154
1173
  return ".".join(
1155
1174
  self.sql(part)
1156
1175
  for part in (
@@ -1822,7 +1841,7 @@ class Generator:
1822
1841
  return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1823
1842
 
1824
1843
  def drop_sql(self, expression: exp.Drop) -> str:
1825
- this = self.sql(expression, "this")
1844
+ tables = self.expressions(expression, key="tables", flat=True)
1826
1845
  expressions = self.expressions(expression, flat=True)
1827
1846
  expressions = f" ({expressions})" if expressions else ""
1828
1847
  kind = expression.args["kind"]
@@ -1844,7 +1863,7 @@ class Generator:
1844
1863
  purge = " PURGE" if expression.args.get("purge") else ""
1845
1864
  sync = " SYNC" if expression.args.get("sync") else ""
1846
1865
  force = " FORCE" if expression.args.get("force") else ""
1847
- 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}"
1848
1867
 
1849
1868
  def set_operation(self, expression: exp.SetOperation) -> str:
1850
1869
  op_type = type(expression)
@@ -2815,7 +2834,18 @@ class Generator:
2815
2834
  and groupings
2816
2835
  and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2817
2836
  ):
2818
- 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}"
2819
2849
 
2820
2850
  return f"{group_by}{groupings}"
2821
2851
 
@@ -4583,7 +4613,15 @@ class Generator:
4583
4613
  return self.binary(expression, "<=")
4584
4614
 
4585
4615
  def mod_sql(self, expression: exp.Mod) -> str:
4586
- 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
4587
4625
 
4588
4626
  def mul_sql(self, expression: exp.Mul) -> str:
4589
4627
  return self.binary(expression, "*")
@@ -5031,6 +5069,12 @@ class Generator:
5031
5069
 
5032
5070
  return self.sql(case)
5033
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
+
5034
5078
  def comprehension_sql(self, expression: exp.Comprehension) -> str:
5035
5079
  this = self.sql(expression, "this")
5036
5080
  expr = self.sql(expression, "expression")
@@ -5391,9 +5435,11 @@ class Generator:
5391
5435
 
5392
5436
  if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5393
5437
  if self.IGNORE_NULLS_BEFORE_ORDER:
5438
+ from sqlglot.optimizer.scope import find_all_in_scope
5439
+
5394
5440
  # The first modifier here will be the one closest to the AggFunc's arg
5395
5441
  mods = sorted(
5396
- expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5442
+ find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5397
5443
  key=lambda x: (
5398
5444
  0
5399
5445
  if isinstance(x, exp.HavingMax)
@@ -6033,8 +6079,8 @@ class Generator:
6033
6079
  options = f" {options}" if options else ""
6034
6080
  kind = self.sql(expression, "kind")
6035
6081
  kind = f" {kind}" if kind else ""
6036
- this = self.sql(expression, "this")
6037
- this = f" {this}" if this else ""
6082
+ tables = self.expressions(expression, key="tables", flat=True)
6083
+ tables = f" {tables}" if tables else ""
6038
6084
  mode = self.sql(expression, "mode")
6039
6085
  mode = f" {mode}" if mode else ""
6040
6086
  properties = self.sql(expression, "properties")
@@ -6043,7 +6089,7 @@ class Generator:
6043
6089
  partition = f" {partition}" if partition else ""
6044
6090
  inner_expression = self.sql(expression, "expression")
6045
6091
  inner_expression = f" {inner_expression}" if inner_expression else ""
6046
- return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
6092
+ return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6047
6093
 
6048
6094
  def xmltable_sql(self, expression: exp.XMLTable) -> str:
6049
6095
  this = self.sql(expression, "this")
@@ -6319,6 +6365,22 @@ class Generator:
6319
6365
  self.unsupported("Unsupported While block syntax")
6320
6366
  return ""
6321
6367
 
6368
+ def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6369
+ self.unsupported("Unsupported Loop block syntax")
6370
+ return ""
6371
+
6372
+ def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6373
+ self.unsupported("Unsupported Repeat block syntax")
6374
+ return ""
6375
+
6376
+ def leave_sql(self, expression: exp.Leave) -> str:
6377
+ self.unsupported("Unsupported Leave syntax")
6378
+ return ""
6379
+
6380
+ def iterate_sql(self, expression: exp.Iterate) -> str:
6381
+ self.unsupported("Unsupported Iterate syntax")
6382
+ return ""
6383
+
6322
6384
  def execute_sql(self, expression: exp.Execute) -> str:
6323
6385
  self.unsupported("Unsupported Execute syntax")
6324
6386
  return ""
@@ -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:
@@ -58,6 +58,15 @@ def _lambda_sql(self, e: exp.Lambda) -> str:
58
58
  return f"lambda {self.expressions(e, flat=True)}: {self.sql(e, 'this')}"
59
59
 
60
60
 
61
+ def _like_sql(self: generator.Generator, e: exp.Like | exp.ILike) -> str:
62
+ sql = self.func(e.key, e.this, e.expression)
63
+
64
+ if e.args.get("negate"):
65
+ sql = f"NOT({sql})"
66
+
67
+ return sql
68
+
69
+
61
70
  def _div_sql(self: generator.Generator, e: exp.Div) -> str:
62
71
  denominator = self.sql(e, "expression")
63
72
 
@@ -66,12 +75,21 @@ def _div_sql(self: generator.Generator, e: exp.Div) -> str:
66
75
 
67
76
  sql = f"DIV({self.sql(e, 'this')}, {denominator})"
68
77
 
69
- if e.args.get("typed"):
70
- sql = f"int({sql})"
78
+ if e.args.get("typed") and not (
79
+ e.this.is_type(*exp.DataType.REAL_TYPES) or e.expression.is_type(*exp.DataType.REAL_TYPES)
80
+ ):
81
+ sql = f"INT({sql})"
71
82
 
72
83
  return sql
73
84
 
74
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
+
75
93
  class PythonGenerator(generator.Generator):
76
94
  TRANSFORMS = {
77
95
  **{klass: _rename for klass in subclasses(exp.__name__, exp.Binary)},
@@ -89,7 +107,9 @@ class PythonGenerator(generator.Generator):
89
107
  ),
90
108
  exp.Distinct: lambda self, e: f"set({self.sql(e, 'this')})",
91
109
  exp.Div: _div_sql,
110
+ exp.DPipe: _dpipe_sql,
92
111
  exp.Extract: lambda self, e: f"EXTRACT('{e.name.lower()}', {self.sql(e, 'expression')})",
112
+ exp.ILike: _like_sql,
93
113
  exp.In: lambda self, e: self.func("IN", e.this, *e.expressions),
94
114
  exp.Interval: lambda self, e: f"INTERVAL({self.sql(e.this)}, '{self.sql(e.unit)}')",
95
115
  exp.Is: lambda self, e: (
@@ -102,6 +122,7 @@ class PythonGenerator(generator.Generator):
102
122
  exp.JSONPathKey: lambda self, e: f"'{self.sql(e.this)}'",
103
123
  exp.JSONPathSubscript: lambda self, e: f"'{e.this}'",
104
124
  exp.Lambda: _lambda_sql,
125
+ exp.Like: _like_sql,
105
126
  exp.Not: lambda self, e: self.func("NOT", e.this),
106
127
  exp.Null: lambda *_: "None",
107
128
  exp.Or: lambda self, e: f"OR(lambda: {self.sql(e.left)}, lambda: {self.sql(e.right)})",
@@ -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