sqlglotc 30.23.0__tar.gz → 30.24.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 (117) hide show
  1. {sqlglotc-30.23.0/sqlglotc.egg-info → sqlglotc-30.24.0}/PKG-INFO +2 -2
  2. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/setup.py +17 -0
  3. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/array.py +1 -5
  4. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/query.py +8 -0
  5. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generator.py +31 -1
  6. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/bigquery.py +142 -1
  7. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/clickhouse.py +2 -1
  8. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/duckdb.py +36 -40
  9. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/fabric.py +5 -0
  10. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/hive.py +4 -0
  11. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/mysql.py +3 -0
  12. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/postgres.py +8 -3
  13. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/presto.py +14 -7
  14. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/snowflake.py +15 -7
  15. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/spark2.py +22 -1
  16. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/starrocks.py +35 -4
  17. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/teradata.py +11 -1
  18. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/helper.py +17 -0
  19. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/annotate_types.py +24 -16
  20. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/qualify_columns.py +33 -9
  21. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/scope.py +8 -7
  22. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/simplify.py +23 -5
  23. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parser.py +145 -32
  24. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/clickhouse.py +6 -0
  25. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/doris.py +3 -3
  26. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/dremio.py +3 -0
  27. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/drill.py +3 -0
  28. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/duckdb.py +34 -8
  29. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/hive.py +13 -0
  30. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/mysql.py +71 -3
  31. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/oracle.py +3 -1
  32. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/postgres.py +3 -1
  33. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/presto.py +2 -0
  34. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/prql.py +2 -8
  35. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/redshift.py +3 -1
  36. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/singlestore.py +3 -0
  37. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/snowflake.py +4 -4
  38. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/spark2.py +4 -1
  39. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/sqlite.py +14 -1
  40. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/starrocks.py +73 -10
  41. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/teradata.py +15 -0
  42. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/trino.py +6 -1
  43. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/tokenizer_core.py +1 -0
  44. {sqlglotc-30.23.0 → sqlglotc-30.24.0/sqlglotc.egg-info}/PKG-INFO +2 -2
  45. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglotc.egg-info/requires.txt +1 -1
  46. sqlglotc-30.24.0/sqlglotc.egg-info/scm_version.json +8 -0
  47. sqlglotc-30.23.0/sqlglotc.egg-info/scm_version.json +0 -8
  48. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/MANIFEST.in +0 -0
  49. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/pyproject.toml +0 -0
  50. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/setup.cfg +0 -0
  51. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/anonymize.py +0 -0
  52. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/errors.py +0 -0
  53. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/executor/table.py +0 -0
  54. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/aggregate.py +0 -0
  55. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/builders.py +0 -0
  56. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/constraints.py +0 -0
  57. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/core.py +0 -0
  58. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/datatypes.py +0 -0
  59. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/ddl.py +0 -0
  60. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/dml.py +0 -0
  61. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/functions.py +0 -0
  62. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/json.py +0 -0
  63. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/math.py +0 -0
  64. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/properties.py +0 -0
  65. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/string.py +0 -0
  66. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/expressions/temporal.py +0 -0
  67. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/athena.py +0 -0
  68. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/databricks.py +0 -0
  69. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/dax.py +0 -0
  70. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/doris.py +0 -0
  71. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/dremio.py +0 -0
  72. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/drill.py +0 -0
  73. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/druid.py +0 -0
  74. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/dune.py +0 -0
  75. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/exasol.py +0 -0
  76. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/materialize.py +0 -0
  77. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/oracle.py +0 -0
  78. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/prql.py +0 -0
  79. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/python.py +0 -0
  80. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/redshift.py +0 -0
  81. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/risingwave.py +0 -0
  82. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/singlestore.py +0 -0
  83. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/solr.py +0 -0
  84. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/spark.py +0 -0
  85. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/sqlite.py +0 -0
  86. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/tableau.py +0 -0
  87. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/trino.py +0 -0
  88. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/generators/tsql.py +0 -0
  89. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/lineage.py +0 -0
  90. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  91. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  92. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  93. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/qualify.py +0 -0
  94. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/qualify_tables.py +0 -0
  95. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/optimizer/resolver.py +0 -0
  96. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/athena.py +0 -0
  97. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/base.py +0 -0
  98. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/bigquery.py +0 -0
  99. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/databricks.py +0 -0
  100. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/dax.py +0 -0
  101. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/druid.py +0 -0
  102. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/dune.py +0 -0
  103. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/exasol.py +0 -0
  104. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/fabric.py +0 -0
  105. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/materialize.py +0 -0
  106. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/risingwave.py +0 -0
  107. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/solr.py +0 -0
  108. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/spark.py +0 -0
  109. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/tableau.py +0 -0
  110. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/parsers/tsql.py +0 -0
  111. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/serde.py +0 -0
  112. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/time.py +0 -0
  113. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglot/trie.py +0 -0
  114. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  115. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  116. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  117. {sqlglotc-30.23.0 → sqlglotc-30.24.0}/sqlglotc.egg-info/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.23.0
3
+ Version: 30.24.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.23.0
10
+ Requires-Dist: sqlglot==30.24.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
@@ -90,6 +90,16 @@ def _source_paths():
90
90
 
91
91
 
92
92
  class build_ext(_build_ext):
93
+ def _remove_obsolete_extensions(self, directory, expected):
94
+ if self.dry_run:
95
+ return
96
+ expected = {os.path.abspath(path) for path in expected}
97
+ for root, _, files in os.walk(directory):
98
+ for filename in files:
99
+ path = os.path.join(root, filename)
100
+ if filename.endswith((".so", ".pyd")) and os.path.abspath(path) not in expected:
101
+ self.execute(os.remove, (path,), f"removing obsolete extension {path}")
102
+
93
103
  def finalize_options(self):
94
104
  super().finalize_options()
95
105
  # Compile in parallel by default; builds driven by pip (wheel/sdist installs)
@@ -97,8 +107,13 @@ class build_ext(_build_ext):
97
107
  if self.parallel is None:
98
108
  self.parallel = os.cpu_count() or 1
99
109
 
110
+ def build_extensions(self):
111
+ self._remove_obsolete_extensions(self.build_lib, self.get_outputs())
112
+ super().build_extensions()
113
+
100
114
  def copy_extensions_to_source(self):
101
115
  """For editable installs, put sqlglot.* .so files in the sqlglot source dir."""
116
+ expected = []
102
117
  for ext in self.extensions:
103
118
  fullname = self.get_ext_fullname(ext.name)
104
119
  filename = self.get_ext_filename(fullname)
@@ -112,7 +127,9 @@ class build_ext(_build_ext):
112
127
  # Place the mypyc runtime helper (e.g., HASH__mypyc) inside sqlglot/.
113
128
  # sqlglot/__init__.py bootstraps it into sys.modules for editable installs.
114
129
  dst = os.path.join(SQLGLOT_SRC, os.path.basename(filename))
130
+ expected.append(dst)
115
131
  self.copy_file(src, dst, level=self.verbose)
132
+ self._remove_obsolete_extensions(SQLGLOT_SRC, expected)
116
133
 
117
134
 
118
135
  class sdist(_sdist):
@@ -98,17 +98,13 @@ class ArraySlice(Expression, Func):
98
98
 
99
99
 
100
100
  class ArraySort(Expression, Func):
101
- arg_types = {"this": True, "expression": False}
101
+ arg_types = {"this": True, "expression": False, "asc": False, "nulls_first": False}
102
102
 
103
103
 
104
104
  class Shuffle(Expression, Func):
105
105
  pass
106
106
 
107
107
 
108
- class SortArray(Expression, Func):
109
- arg_types = {"this": True, "asc": False, "nulls_first": False}
110
-
111
-
112
108
  # Array predicates / search
113
109
 
114
110
 
@@ -1938,6 +1938,10 @@ class WindowSpec(Expression):
1938
1938
  }
1939
1939
 
1940
1940
 
1941
+ class PartitionByExcluding(Expression):
1942
+ arg_types = {"expressions": True}
1943
+
1944
+
1941
1945
  class PreWhere(Expression):
1942
1946
  pass
1943
1947
 
@@ -2020,6 +2024,10 @@ class DropPartition(Expression):
2020
2024
  arg_types = {"expressions": True, "exists": False}
2021
2025
 
2022
2026
 
2027
+ class ReplaceColumns(Expression):
2028
+ arg_types = {"expressions": True}
2029
+
2030
+
2023
2031
  class ReplacePartition(Expression):
2024
2032
  arg_types = {"expression": True, "source": True}
2025
2033
 
@@ -2178,6 +2178,7 @@ class Generator:
2178
2178
  property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2179
2179
  if not property_name:
2180
2180
  self.unsupported(f"Unsupported property {expression.key}")
2181
+ return ""
2181
2182
 
2182
2183
  return f"{property_name}={self.sql(expression, 'this')}"
2183
2184
 
@@ -2319,6 +2320,10 @@ class Generator:
2319
2320
  override = " OVERRIDE" if expression.args.get("override") else ""
2320
2321
  return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2321
2322
 
2323
+ def lockingstatement_sql(self, expression: exp.LockingStatement) -> str:
2324
+ self.unsupported("LOCKING request modifier is not supported")
2325
+ return self.sql(expression, "expression")
2326
+
2322
2327
  def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2323
2328
  data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2324
2329
  statistics = expression.args.get("statistics")
@@ -2561,6 +2566,10 @@ class Generator:
2561
2566
  expression: exp.TableSample,
2562
2567
  tablesample_keyword: str | None = None,
2563
2568
  ) -> str:
2569
+ if expression.expressions:
2570
+ self.unsupported("Multiple sample sets are not supported")
2571
+ return ""
2572
+
2564
2573
  method = self.sql(expression, "method")
2565
2574
  method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2566
2575
  numerator = self.sql(expression, "bucket_numerator")
@@ -3638,6 +3647,9 @@ class Generator:
3638
3647
  partition = self.expressions(expression, key="partition_by", flat=True)
3639
3648
  return f"PARTITION BY {partition}" if partition else ""
3640
3649
 
3650
+ def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str:
3651
+ return f"EXCLUDING {self.expressions(expression, flat=True)}"
3652
+
3641
3653
  def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3642
3654
  kind = self.sql(expression, "kind")
3643
3655
  start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
@@ -4517,6 +4529,9 @@ class Generator:
4517
4529
  def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4518
4530
  return f"ADD {self.expressions(expression, indent=False)}"
4519
4531
 
4532
+ def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str:
4533
+ return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})"
4534
+
4520
4535
  def addpartition_sql(self, expression: exp.AddPartition) -> str:
4521
4536
  exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4522
4537
  location = self.sql(expression, "location")
@@ -4771,14 +4786,22 @@ class Generator:
4771
4786
  sqls: list[str] = []
4772
4787
  stack: list[None | str | exp.Expr] = [expression]
4773
4788
  binary_type = type(expression)
4789
+ is_is = binary_type is exp.Is and op in ("IS", "IS NOT")
4790
+ negate = expression.args.get("negate")
4774
4791
 
4775
4792
  while stack:
4776
4793
  node = stack.pop()
4777
4794
 
4778
- if type(node) is binary_type:
4795
+ if type(node) is binary_type and (
4796
+ node is expression
4797
+ or (is_is and not isinstance(node.expression, exp.Boolean))
4798
+ or node.args.get("negate") == negate
4799
+ ):
4779
4800
  op_func = node.args.get("operator")
4780
4801
  if op_func:
4781
4802
  op = f"OPERATOR({self.sql(op_func)})"
4803
+ elif is_is:
4804
+ op = "IS NOT" if node.args.get("negate") else "IS"
4782
4805
 
4783
4806
  stack.append(node.args.get("expression"))
4784
4807
  stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
@@ -6053,6 +6076,13 @@ class Generator:
6053
6076
  exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6054
6077
  )
6055
6078
 
6079
+ def arraysort_sql(self, expression: exp.ArraySort) -> str:
6080
+ asc = expression.args.get("asc")
6081
+ nulls_first = expression.args.get("nulls_first")
6082
+ if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()):
6083
+ self.unsupported("ARRAY_SORT only sorts ascending with NULLs last")
6084
+ return self.func("ARRAY_SORT", expression.this, expression.expression)
6085
+
6056
6086
  def arraysize_sql(self, expression: exp.ArraySize) -> str:
6057
6087
  dim = expression.expression
6058
6088
 
@@ -3,10 +3,13 @@ from __future__ import annotations
3
3
  import logging
4
4
  import re
5
5
  import typing as t
6
+ from decimal import Decimal
7
+ from fractions import Fraction
6
8
 
7
9
  from sqlglot import exp, generator, transforms
8
10
  from sqlglot.dialects.dialect import (
9
11
  arg_max_or_min_no_count,
12
+ array_sort_order,
10
13
  date_add_interval_sql,
11
14
  datestrtodate_sql,
12
15
  filter_array_using_unnest,
@@ -176,6 +179,90 @@ def _array_contains_sql(self: BigQueryGenerator, expression: exp.ArrayContains)
176
179
  )
177
180
 
178
181
 
182
+ def _widen_decimal(expression: exp.Expr) -> exp.Expr:
183
+ # NUMERIC holds at most 29 integer and 9 fractional digits, e.g. DECIMAL(38, 0) needs BIGNUMERIC
184
+ for node in expression.find_all(exp.DataType):
185
+ if node.is_type(exp.DType.DECIMAL) and node.expressions:
186
+ precision, *rest = (param.this for param in node.expressions)
187
+ scale = rest[0] if rest else exp.Literal.number(0)
188
+ if precision.is_int and scale.is_int:
189
+ scale_digits = int(scale.name)
190
+ if int(precision.name) - scale_digits > 29 or scale_digits > 9:
191
+ node.set("this", exp.DType.BIGDECIMAL)
192
+ return expression
193
+
194
+
195
+ def _extract_part(part: str) -> t.Callable[[BigQueryGenerator, exp.Func], str]:
196
+ return lambda self, e: self.sql(exp.func("EXTRACT", exp.var(part), e.this, copy=False))
197
+
198
+
199
+ def _day_of_week_iso_sql(self: BigQueryGenerator, expression: exp.DayOfWeekIso) -> str:
200
+ # DAYOFWEEK is Sunday=1..Saturday=7, ISO is Monday=1..Sunday=7
201
+ template: exp.Expr = exp.maybe_parse("(MOD(EXTRACT(DAYOFWEEK FROM :this) + 5, 7) + 1)")
202
+ return self.sql(exp.replace_placeholders(template, this=expression.this))
203
+
204
+
205
+ def _array_agg_fold_sql(
206
+ self: BigQueryGenerator, expression: exp.ArrayDistinct | exp.ArraySort
207
+ ) -> str:
208
+ distinct = False
209
+ ignore_nulls = False
210
+ sort: exp.ArraySort | None = None
211
+ agg: exp.Expr = expression
212
+ while isinstance(agg, (exp.ArrayDistinct, exp.ArraySort)) and not agg.expression:
213
+ if isinstance(agg, exp.ArrayDistinct):
214
+ distinct = True
215
+ ignore_nulls |= agg.args.get("check_null") is False
216
+ elif sort is None:
217
+ sort = agg
218
+ agg = agg.this
219
+
220
+ order = array_sort_order(sort) if sort else None
221
+
222
+ # An ARRAY_AGG with its own ORDER BY or LIMIT can't take another
223
+ if (
224
+ not isinstance(agg, exp.ArrayAgg)
225
+ or isinstance(agg.this, (exp.Order, exp.Limit))
226
+ or (sort is not None and order is None)
227
+ ):
228
+ self.unsupported(
229
+ f"{expression.sql_name()} is only supported on an ARRAY_AGG without ORDER BY or LIMIT and with a literal order"
230
+ )
231
+ return self.func(expression.sql_name(), expression.this, expression.expression)
232
+
233
+ value = agg.this
234
+ if isinstance(value, exp.Distinct):
235
+ distinct, value = True, value.expressions[0]
236
+
237
+ folded: exp.Expr = exp.Distinct(expressions=[value]) if distinct else value
238
+ if order is not None:
239
+ asc, nulls_first = order
240
+ keys = [exp.Ordered(this=value.copy(), desc=not asc or None, nulls_first=asc)]
241
+
242
+ # Aggregate ORDER BY only puts NULLs first when ascending, so other placements need a key
243
+ if not ignore_nulls and nulls_first is not None and nulls_first != asc:
244
+ if distinct:
245
+ self.unsupported("ARRAY_AGG(DISTINCT) only puts NULLs first when ascending")
246
+ else:
247
+ is_null = value.copy().is_(exp.null())
248
+ keys.insert(
249
+ 0,
250
+ exp.Ordered(
251
+ this=is_null, desc=nulls_first or None, nulls_first=not nulls_first
252
+ ),
253
+ )
254
+ folded = exp.Order(this=folded, expressions=keys)
255
+ agg.set("this", folded)
256
+ if ignore_nulls:
257
+ # All-NULL input yields [], but an empty input still yields NULL.
258
+ agg = exp.If(
259
+ this=exp.Count(this=exp.Star()).eq(0),
260
+ true=exp.null(),
261
+ false=exp.func("COALESCE", exp.IgnoreNulls(this=agg), exp.array()),
262
+ )
263
+ return self.sql(agg)
264
+
265
+
179
266
  def _ts_or_ds_add_sql(self: BigQueryGenerator, expression: exp.TsOrDsAdd) -> str:
180
267
  return date_add_interval_sql("DATE", "ADD")(self, ts_or_ds_add_cast(expression))
181
268
 
@@ -311,6 +398,8 @@ class BigQueryGenerator(generator.Generator):
311
398
  exp.ArgMin: arg_max_or_min_no_count("MIN_BY"),
312
399
  exp.Array: inline_array_unless_query,
313
400
  exp.ArrayContains: _array_contains_sql,
401
+ exp.ArrayDistinct: _array_agg_fold_sql,
402
+ exp.ArraySort: _array_agg_fold_sql,
314
403
  exp.ArrayFilter: filter_array_using_unnest,
315
404
  exp.ArrayRemove: filter_array_using_unnest,
316
405
  exp.BitwiseAndAgg: rename_func("BIT_AND"),
@@ -318,7 +407,9 @@ class BigQueryGenerator(generator.Generator):
318
407
  exp.BitwiseXorAgg: rename_func("BIT_XOR"),
319
408
  exp.BitwiseCount: rename_func("BIT_COUNT"),
320
409
  exp.ByteLength: rename_func("BYTE_LENGTH"),
321
- exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
410
+ exp.Cast: transforms.preprocess(
411
+ [_widen_decimal, transforms.remove_precision_parameterized_types]
412
+ ),
322
413
  exp.CollateProperty: lambda self, e: (
323
414
  f"DEFAULT COLLATE {self.sql(e, 'this')}"
324
415
  if e.args.get("default")
@@ -336,6 +427,10 @@ class BigQueryGenerator(generator.Generator):
336
427
  exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
337
428
  exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
338
429
  exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"),
430
+ exp.Day: _extract_part("DAY"),
431
+ exp.DayOfMonth: _extract_part("DAY"),
432
+ exp.DayOfWeekIso: _day_of_week_iso_sql,
433
+ exp.DayOfYear: _extract_part("DAYOFYEAR"),
339
434
  exp.Filter: no_filter_sql,
340
435
  exp.FromTimeZone: lambda self, e: self.func(
341
436
  "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
@@ -346,6 +441,7 @@ class BigQueryGenerator(generator.Generator):
346
441
  ),
347
442
  exp.Hex: lambda self, e: self.func("UPPER", self.func("TO_HEX", self.sql(e, "this"))),
348
443
  exp.HexString: lambda self, e: self.hexstring_sql(e, binary_function_repr="FROM_HEX"),
444
+ exp.Hour: _extract_part("HOUR"),
349
445
  exp.If: if_sql(false_value="NULL"),
350
446
  exp.ILike: no_ilike_sql,
351
447
  exp.IntDiv: rename_func("DIV"),
@@ -366,12 +462,14 @@ class BigQueryGenerator(generator.Generator):
366
462
  exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
367
463
  exp.MD5Digest: rename_func("MD5"),
368
464
  exp.Min: min_or_least,
465
+ exp.Month: _extract_part("MONTH"),
369
466
  exp.Normalize: lambda self, e: self.func(
370
467
  "NORMALIZE_AND_CASEFOLD" if e.args.get("is_casefold") else "NORMALIZE",
371
468
  e.this,
372
469
  e.args.get("form"),
373
470
  ),
374
471
  exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
472
+ exp.Quarter: _extract_part("QUARTER"),
375
473
  exp.RegexpExtract: lambda self, e: self.func(
376
474
  "REGEXP_EXTRACT",
377
475
  e.this,
@@ -431,6 +529,8 @@ class BigQueryGenerator(generator.Generator):
431
529
  exp.Uuid: lambda *_: "GENERATE_UUID()",
432
530
  exp.Values: _derived_table_values_to_unnest,
433
531
  exp.VariancePop: rename_func("VAR_POP"),
532
+ exp.WeekOfYear: _extract_part("ISOWEEK"),
533
+ exp.Year: _extract_part("YEAR"),
434
534
  exp.SafeDivide: rename_func("SAFE_DIVIDE"),
435
535
  }
436
536
 
@@ -582,6 +682,47 @@ class BigQueryGenerator(generator.Generator):
582
682
  "within",
583
683
  }
584
684
 
685
+ def frombase_sql(self, expression: exp.FromBase) -> str:
686
+ # Hex is the only base BigQuery can cast from, and the sign goes before the prefix, e.g. -0xff
687
+ if expression.expression.name != "16":
688
+ self.unsupported("FROM_BASE is only supported for base 16")
689
+ return self.function_fallback_sql(expression)
690
+
691
+ prefixed = exp.func(
692
+ "REGEXP_REPLACE",
693
+ expression.this,
694
+ exp.Literal.string("^([+-]?)"),
695
+ exp.Literal.string("\\10x"),
696
+ copy=False,
697
+ )
698
+ return self.sql(exp.cast(prefixed, exp.DType.BIGINT, copy=False))
699
+
700
+ def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
701
+ node: exp.Expr = expression
702
+ while node.parent and not isinstance(node.parent, (exp.Window, exp.AggFunc, exp.Select)):
703
+ node = node.parent
704
+ if isinstance(node.parent, exp.Window) and node.arg_key == "this":
705
+ self.unsupported("APPROX_QUANTILES does not support window functions")
706
+ return self.function_fallback_sql(expression)
707
+
708
+ # The k/n quantile is APPROX_QUANTILES(x, n)[k], e.g. 0.95 -> n = 20, k = 19. n is at most
709
+ # 100,000, so the quantile has at most 5 decimal places, and Decimal comparisons are exact
710
+ quantile = expression.args["quantile"]
711
+ weight = expression.args.get("weight")
712
+ value = Decimal(quantile.to_py()) if quantile.is_number else None
713
+ if (
714
+ value is None
715
+ or not 0 <= value <= 1
716
+ or round(value, 5) != value
717
+ or (weight and (not weight.is_number or weight.to_py() != 1))
718
+ ):
719
+ self.unsupported("APPROX_QUANTILE needs a literal quantile and no weight")
720
+ return self.function_fallback_sql(expression)
721
+
722
+ fraction = Fraction(value)
723
+ quantiles = exp.func("APPROX_QUANTILES", expression.this, fraction.denominator, copy=False)
724
+ return self.sql(quantiles[fraction.numerator])
725
+
585
726
  def weekstart_sql(self, expression: exp.WeekStart) -> str:
586
727
  if expression.this.name.upper() == "SUNDAY":
587
728
  # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
@@ -688,7 +688,8 @@ class ClickHouseGenerator(generator.Generator):
688
688
  comment_prop = expression.find(exp.SchemaCommentProperty)
689
689
  if comment_prop:
690
690
  comment_prop.pop()
691
- query.replace(exp.paren(query))
691
+ if not isinstance(query, exp.Subquery):
692
+ query.replace(exp.paren(query))
692
693
  else:
693
694
  comment_prop = None
694
695
 
@@ -7,6 +7,7 @@ import typing as t
7
7
 
8
8
  from sqlglot import exp, generator, transforms
9
9
  from sqlglot.dialects.dialect import (
10
+ array_sort_order,
10
11
  DATETIME_DELTA,
11
12
  JSON_EXTRACT_TYPE,
12
13
  approx_count_distinct_sql,
@@ -548,9 +549,22 @@ def _array_remove_at_sql(self: DuckDBGenerator, expression: exp.ArrayRemoveAt) -
548
549
  )
549
550
 
550
551
 
551
- @unsupported_args(("expression", "DuckDB's ARRAY_SORT does not support a comparator."))
552
+ @unsupported_args(("expression", "DuckDB's LIST_SORT does not support a comparator."))
552
553
  def _array_sort_sql(self: DuckDBGenerator, expression: exp.ArraySort) -> str:
553
- return self.func("ARRAY_SORT", expression.this)
554
+ order = array_sort_order(expression)
555
+ if order is None:
556
+ self.unsupported("LIST_SORT only takes literal orders")
557
+ return self.func("LIST_SORT", expression.this)
558
+
559
+ asc, nulls_first = order
560
+ if not nulls_first:
561
+ return self.func("LIST_SORT" if asc else "ARRAY_REVERSE_SORT", expression.this)
562
+ return self.func(
563
+ "LIST_SORT",
564
+ expression.this,
565
+ exp.Literal.string("ASC" if asc else "DESC"),
566
+ exp.Literal.string("NULLS FIRST"),
567
+ )
554
568
 
555
569
 
556
570
  def _array_contains_sql(self: DuckDBGenerator, expression: exp.ArrayContains) -> str:
@@ -1858,16 +1872,16 @@ class DuckDBGenerator(generator.Generator):
1858
1872
  "union",
1859
1873
  "all",
1860
1874
  "when",
1861
- "in_p",
1875
+ "in",
1862
1876
  "default",
1863
- "create_p",
1877
+ "create",
1864
1878
  "window",
1865
1879
  "asymmetric",
1866
1880
  "to",
1867
1881
  "else",
1868
1882
  "localtime",
1869
1883
  "from",
1870
- "end_p",
1884
+ "end",
1871
1885
  "select",
1872
1886
  "current_date",
1873
1887
  "foreign",
@@ -1879,7 +1893,7 @@ class DuckDBGenerator(generator.Generator):
1879
1893
  "references",
1880
1894
  "fetch",
1881
1895
  "limit",
1882
- "group_p",
1896
+ "group",
1883
1897
  "leading",
1884
1898
  "into",
1885
1899
  "collate",
@@ -1887,13 +1901,13 @@ class DuckDBGenerator(generator.Generator):
1887
1901
  "do",
1888
1902
  "then",
1889
1903
  "localtimestamp",
1890
- "check_p",
1891
- "lateral_p",
1904
+ "check",
1905
+ "lateral",
1892
1906
  "current_role",
1893
1907
  "where",
1894
- "asc_p",
1908
+ "asc",
1895
1909
  "placing",
1896
- "desc_p",
1910
+ "desc",
1897
1911
  "user",
1898
1912
  "unique",
1899
1913
  "initially",
@@ -1904,9 +1918,9 @@ class DuckDBGenerator(generator.Generator):
1904
1918
  "any",
1905
1919
  "only",
1906
1920
  "deferrable",
1907
- "null_p",
1921
+ "null",
1908
1922
  "current_time",
1909
- "true_p",
1923
+ "true",
1910
1924
  "table",
1911
1925
  "case",
1912
1926
  "trailing",
@@ -1914,7 +1928,7 @@ class DuckDBGenerator(generator.Generator):
1914
1928
  "for",
1915
1929
  "on",
1916
1930
  "distinct",
1917
- "false_p",
1931
+ "false",
1918
1932
  "not",
1919
1933
  "constraint",
1920
1934
  "current_timestamp",
@@ -1930,6 +1944,15 @@ class DuckDBGenerator(generator.Generator):
1930
1944
  "using",
1931
1945
  "order",
1932
1946
  "current_catalog",
1947
+ "describe",
1948
+ "lambda",
1949
+ "pivot",
1950
+ "pivot_longer",
1951
+ "pivot_wider",
1952
+ "qualify",
1953
+ "show",
1954
+ "summarize",
1955
+ "unpivot",
1933
1956
  }
1934
1957
 
1935
1958
  UNWRAPPED_INTERVAL_VALUES = (exp.Literal, exp.Paren)
@@ -2674,33 +2697,6 @@ class DuckDBGenerator(generator.Generator):
2674
2697
  self.unsupported("SOUNDEX is not supported in DuckDB")
2675
2698
  return self.func("SOUNDEX", expression.this)
2676
2699
 
2677
- def sortarray_sql(self, expression: exp.SortArray) -> str:
2678
- arr = expression.this
2679
- asc = expression.args.get("asc")
2680
- nulls_first = expression.args.get("nulls_first")
2681
-
2682
- if not isinstance(asc, exp.Boolean) and not isinstance(nulls_first, exp.Boolean):
2683
- return self.func("LIST_SORT", arr, asc, nulls_first)
2684
-
2685
- nulls_are_first = nulls_first == exp.true()
2686
- nulls_first_sql = exp.Literal.string("NULLS FIRST") if nulls_are_first else None
2687
-
2688
- if not isinstance(asc, exp.Boolean):
2689
- return self.func("LIST_SORT", arr, asc, nulls_first_sql)
2690
-
2691
- descending = asc == exp.false()
2692
-
2693
- if not descending and not nulls_are_first:
2694
- return self.func("LIST_SORT", arr)
2695
- if not nulls_are_first:
2696
- return self.func("ARRAY_REVERSE_SORT", arr)
2697
- return self.func(
2698
- "LIST_SORT",
2699
- arr,
2700
- exp.Literal.string("DESC" if descending else "ASC"),
2701
- exp.Literal.string("NULLS FIRST"),
2702
- )
2703
-
2704
2700
  def install_sql(self, expression: exp.Install) -> str:
2705
2701
  force = "FORCE " if expression.args.get("force") else ""
2706
2702
  this = self.sql(expression, "this")
@@ -121,6 +121,11 @@ class FabricGenerator(TSQLGenerator):
121
121
  # Generate the AT TIME ZONE expression (which will handle the inner cast conversion)
122
122
  at_time_zone_sql = super().attimezone_sql(expression)
123
123
 
124
+ # Don't wrap again if it's already cast to DATETIME2, e.g. when re-parsing our own output
125
+ parent = expression.parent
126
+ if isinstance(parent, exp.Cast) and parent.to.is_type(exp.DType.DATETIME2):
127
+ return at_time_zone_sql
128
+
124
129
  # Wrap it in an outer cast to DATETIME2
125
130
  return f"CAST({at_time_zone_sql} AS DATETIME2({precision}))"
126
131
 
@@ -6,6 +6,7 @@ from functools import partial
6
6
 
7
7
  from sqlglot import exp, generator, transforms
8
8
  from sqlglot.dialects.dialect import (
9
+ array_sort_order,
9
10
  DATE_ADD_OR_SUB,
10
11
  approx_count_distinct_sql,
11
12
  arg_max_or_min_no_count,
@@ -162,6 +163,9 @@ def _date_diff_sql(self: HiveGenerator, expression: exp.DateDiff | exp.TsOrDsDif
162
163
 
163
164
  @generator.unsupported_args(("expression", "Hive's SORT_ARRAY does not support a comparator."))
164
165
  def _array_sort_sql(self: HiveGenerator, expression: exp.ArraySort) -> str:
166
+ asc, nulls_first = array_sort_order(expression) or (False, False)
167
+ if not asc or nulls_first is False:
168
+ self.unsupported("Hive's SORT_ARRAY only sorts ascending with NULLs first")
165
169
  return self.func("SORT_ARRAY", expression.this)
166
170
 
167
171
 
@@ -195,6 +195,7 @@ class MySQLGenerator(generator.Generator):
195
195
  transforms.unnest_generate_date_array_using_recursive_cte,
196
196
  ]
197
197
  ),
198
+ exp.Stddev: rename_func("STDDEV_SAMP"),
198
199
  exp.StrPosition: lambda self, e: strposition_sql(
199
200
  self, e, func_name="LOCATE", supports_position=True
200
201
  ),
@@ -228,6 +229,8 @@ class MySQLGenerator(generator.Generator):
228
229
  exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
229
230
  exp.Unicode: lambda self, e: f"ORD(CONVERT({self.sql(e.this)} USING utf32))",
230
231
  exp.UnixToTime: _unix_to_time_sql,
232
+ exp.Variance: rename_func("VAR_SAMP"),
233
+ exp.VariancePop: rename_func("VAR_POP"),
231
234
  exp.Week: remove_ts_or_ds_to_date(),
232
235
  exp.WeekOfYear: remove_ts_or_ds_to_date(rename_func("WEEKOFYEAR")),
233
236
  exp.Year: remove_ts_or_ds_to_date(),
@@ -40,7 +40,7 @@ from sqlglot.dialects.dialect import (
40
40
  ts_or_ds_add_cast,
41
41
  )
42
42
  from sqlglot.generator import unsupported_args
43
- from sqlglot.helper import ensure_list, seq_get
43
+ from sqlglot.helper import ensure_list, seq_get, to_int
44
44
 
45
45
 
46
46
  DATE_DIFF_FACTOR = {
@@ -579,8 +579,13 @@ class PostgresGenerator(generator.Generator):
579
579
 
580
580
  this = expression.this
581
581
  if unit.startswith("quarter") and isinstance(this, exp.Literal):
582
- this.replace(exp.Literal.string(int(this.to_py()) * 3))
583
- expression.args["unit"].replace(exp.var("MONTH"))
582
+ # Postgres has no QUARTER unit and no exponents in interval strings
583
+ quarters = to_int(this.to_py())
584
+ if quarters is None:
585
+ self.unsupported("Postgres intervals need a whole number of quarters")
586
+ else:
587
+ this.replace(exp.Literal.string(quarters * 3))
588
+ expression.args["unit"].replace(exp.var("MONTH"))
584
589
 
585
590
  return super().interval_sql(expression)
586
591