sqlglotc 30.23.0__tar.gz → 30.25.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.25.0}/PKG-INFO +2 -2
  2. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/pyproject.toml +1 -0
  3. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/setup.py +17 -0
  4. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/array.py +5 -5
  5. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/builders.py +1 -1
  6. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/constraints.py +2 -1
  7. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/core.py +7 -8
  8. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/datatypes.py +3 -1
  9. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/ddl.py +8 -0
  10. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/query.py +16 -3
  11. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generator.py +104 -21
  12. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/bigquery.py +143 -1
  13. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/clickhouse.py +9 -8
  14. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/databricks.py +1 -0
  15. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/duckdb.py +107 -46
  16. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/exasol.py +3 -6
  17. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/fabric.py +5 -0
  18. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/hive.py +8 -1
  19. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/mysql.py +3 -0
  20. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/oracle.py +0 -3
  21. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/postgres.py +43 -5
  22. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/presto.py +21 -9
  23. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/python.py +5 -0
  24. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/snowflake.py +18 -7
  25. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/spark2.py +22 -1
  26. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/sqlite.py +6 -2
  27. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/starrocks.py +35 -4
  28. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/teradata.py +11 -1
  29. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/tsql.py +4 -1
  30. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/helper.py +17 -0
  31. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/annotate_types.py +171 -106
  32. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/canonicalize_internal_names.py +11 -10
  33. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/qualify_columns.py +98 -138
  34. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/resolver.py +57 -8
  35. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/scope.py +24 -9
  36. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/simplify.py +28 -6
  37. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parser.py +186 -44
  38. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/clickhouse.py +11 -5
  39. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/doris.py +3 -3
  40. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/dremio.py +3 -0
  41. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/drill.py +3 -0
  42. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/duckdb.py +115 -10
  43. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/hive.py +13 -0
  44. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/mysql.py +71 -3
  45. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/oracle.py +16 -1
  46. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/postgres.py +36 -6
  47. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/presto.py +2 -0
  48. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/prql.py +2 -8
  49. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/redshift.py +3 -1
  50. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/singlestore.py +3 -0
  51. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/snowflake.py +4 -4
  52. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/spark2.py +4 -1
  53. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/sqlite.py +46 -2
  54. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/starrocks.py +73 -10
  55. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/teradata.py +15 -0
  56. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/trino.py +6 -1
  57. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/tokenizer_core.py +3 -0
  58. {sqlglotc-30.23.0 → sqlglotc-30.25.0/sqlglotc.egg-info}/PKG-INFO +2 -2
  59. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglotc.egg-info/requires.txt +1 -1
  60. sqlglotc-30.25.0/sqlglotc.egg-info/scm_version.json +8 -0
  61. sqlglotc-30.23.0/sqlglotc.egg-info/scm_version.json +0 -8
  62. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/MANIFEST.in +0 -0
  63. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/setup.cfg +0 -0
  64. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/anonymize.py +0 -0
  65. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/errors.py +0 -0
  66. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/executor/table.py +0 -0
  67. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/aggregate.py +0 -0
  68. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/dml.py +0 -0
  69. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/functions.py +0 -0
  70. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/json.py +0 -0
  71. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/math.py +0 -0
  72. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/properties.py +0 -0
  73. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/string.py +0 -0
  74. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/expressions/temporal.py +0 -0
  75. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/athena.py +0 -0
  76. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/dax.py +0 -0
  77. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/doris.py +0 -0
  78. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/dremio.py +0 -0
  79. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/drill.py +0 -0
  80. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/druid.py +0 -0
  81. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/dune.py +0 -0
  82. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/materialize.py +0 -0
  83. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/prql.py +0 -0
  84. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/redshift.py +0 -0
  85. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/risingwave.py +0 -0
  86. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/singlestore.py +0 -0
  87. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/solr.py +0 -0
  88. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/spark.py +0 -0
  89. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/tableau.py +0 -0
  90. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/generators/trino.py +0 -0
  91. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/lineage.py +0 -0
  92. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  93. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  94. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/qualify.py +0 -0
  95. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/optimizer/qualify_tables.py +0 -0
  96. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/athena.py +0 -0
  97. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/base.py +0 -0
  98. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/bigquery.py +0 -0
  99. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/databricks.py +0 -0
  100. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/dax.py +0 -0
  101. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/druid.py +0 -0
  102. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/dune.py +0 -0
  103. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/exasol.py +0 -0
  104. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/fabric.py +0 -0
  105. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/materialize.py +0 -0
  106. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/risingwave.py +0 -0
  107. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/solr.py +0 -0
  108. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/spark.py +0 -0
  109. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/tableau.py +0 -0
  110. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/parsers/tsql.py +0 -0
  111. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/serde.py +0 -0
  112. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/time.py +0 -0
  113. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglot/trie.py +0 -0
  114. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  115. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  116. {sqlglotc-30.23.0 → sqlglotc-30.25.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  117. {sqlglotc-30.23.0 → sqlglotc-30.25.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.25.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.25.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
@@ -28,6 +28,7 @@ include-package-data = false
28
28
 
29
29
  [tool.setuptools_scm]
30
30
  root = ".."
31
+ write_to_source = true
31
32
  fallback_version = "0.0.0"
32
33
  local_scheme = "no-local-version"
33
34
 
@@ -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
 
@@ -258,6 +254,10 @@ class SplitToTable(Expression, Func, UDTF):
258
254
  arg_types = {"this": True, "expression": True}
259
255
 
260
256
 
257
+ class StrtokSplitToTable(Expression, Func, UDTF):
258
+ arg_types = {"this": True, "expression": False}
259
+
260
+
261
261
  class Stack(Expression, Func, UDTF):
262
262
  arg_types = {"this": True, "expressions": True}
263
263
  is_var_len_args = True
@@ -921,7 +921,7 @@ def replace_placeholders(expression: Expr, *args: object, **kwargs: t.Any) -> Ex
921
921
  def _replace_placeholders(node: Expr, args: Iterator[object], **kwargs: object) -> Expr:
922
922
  if isinstance(node, Placeholder):
923
923
  if node.this:
924
- new_name = kwargs.get(node.this)
924
+ new_name = kwargs.get(node.name)
925
925
  if new_name is not None:
926
926
  return _replace_placeholder(node, convert(new_name))
927
927
  else:
@@ -161,7 +161,7 @@ class OnUpdateColumnConstraint(Expression, ColumnConstraintKind):
161
161
 
162
162
 
163
163
  class PrimaryKeyColumnConstraint(Expression, ColumnConstraintKind):
164
- arg_types = {"desc": False, "options": False}
164
+ arg_types = {"desc": False, "options": False, "include": False}
165
165
 
166
166
 
167
167
  class TitleColumnConstraint(Expression, ColumnConstraintKind):
@@ -175,6 +175,7 @@ class UniqueColumnConstraint(Expression, ColumnConstraintKind):
175
175
  "on_conflict": False,
176
176
  "nulls": False,
177
177
  "options": False,
178
+ "include": False,
178
179
  }
179
180
 
180
181
 
@@ -1734,13 +1734,13 @@ class Column(Expression, Condition):
1734
1734
  return self.name
1735
1735
 
1736
1736
  @property
1737
- def parts(self) -> list[Identifier | Star]:
1737
+ def parts(self) -> list[Expr]:
1738
1738
  """Return the parts of a column in order catalog, db, table, name."""
1739
1739
  return [
1740
1740
  self.args[part] for part in ("catalog", "db", "table", "this") if self.args.get(part)
1741
1741
  ]
1742
1742
 
1743
- def to_dot(self, include_dots: bool = True) -> Dot | Identifier | Star:
1743
+ def to_dot(self, include_dots: bool = True) -> Expr:
1744
1744
  """Converts the column into a dot expression."""
1745
1745
  parts = self.parts
1746
1746
  parent = self.parent
@@ -3071,7 +3071,7 @@ def alias_(
3071
3071
 
3072
3072
  @t.overload
3073
3073
  def column(
3074
- col: str | Identifier,
3074
+ col: str | Expr,
3075
3075
  table: str | Identifier | None = None,
3076
3076
  db: str | Identifier | None = None,
3077
3077
  catalog: str | Identifier | None = None,
@@ -3085,7 +3085,7 @@ def column(
3085
3085
 
3086
3086
  @t.overload
3087
3087
  def column(
3088
- col: str | Identifier | Star,
3088
+ col: str | Expr,
3089
3089
  table: str | Identifier | None = None,
3090
3090
  db: str | Identifier | None = None,
3091
3091
  catalog: str | Identifier | None = None,
@@ -3111,19 +3111,18 @@ def column(
3111
3111
  Build a Column.
3112
3112
 
3113
3113
  Args:
3114
- col: Column name.
3114
+ col: Column name or expression.
3115
3115
  table: Table name.
3116
3116
  db: Database name.
3117
3117
  catalog: Catalog name.
3118
3118
  fields: Additional fields using dots.
3119
3119
  quoted: Whether to force quotes on the column's identifiers.
3120
- copy: Whether to copy identifiers if passed in.
3120
+ copy: Whether to copy expressions if passed in.
3121
3121
 
3122
3122
  Returns:
3123
3123
  The new Column instance.
3124
3124
  """
3125
- if not isinstance(col, Star):
3126
- col = to_identifier(col, quoted=quoted, copy=copy)
3125
+ col = maybe_copy(col, copy) if isinstance(col, Expr) else to_identifier(col, quoted=quoted)
3127
3126
 
3128
3127
  this: Column | Dot = Column(
3129
3128
  this=col,
@@ -461,7 +461,9 @@ class IntervalSpan(DataType):
461
461
 
462
462
 
463
463
  class Interval(_TimeUnit):
464
- arg_types = {"this": False, "unit": False}
464
+ # fraction_in_string: a fractional amount was written with its unit in one string, e.g.
465
+ # INTERVAL '1.5 DAY', which differs from INTERVAL '1.5' DAY in e.g. DuckDB and Postgres
466
+ arg_types = {"this": False, "unit": False, "fraction_in_string": False}
465
467
 
466
468
 
467
469
  DATA_TYPE = t.Union[str, Identifier, Dot, DataType, DType]
@@ -131,6 +131,14 @@ class Describe(Expression):
131
131
  }
132
132
 
133
133
 
134
+ class ConnectTo(Expression):
135
+ pass
136
+
137
+
138
+ class Disconnect(Expression):
139
+ arg_types = {}
140
+
141
+
134
142
  class Attach(Expression):
135
143
  arg_types = {"this": True, "exists": False, "expressions": False}
136
144
 
@@ -663,7 +663,7 @@ class GroupingSets(Expression):
663
663
 
664
664
 
665
665
  class Lambda(Expression):
666
- arg_types = {"this": True, "expressions": True, "colon": False}
666
+ arg_types = {"this": True, "expressions": True}
667
667
 
668
668
 
669
669
  class Limit(Expression):
@@ -699,8 +699,9 @@ class Join(Expression):
699
699
  "method": False,
700
700
  "global_": False,
701
701
  "hint": False,
702
- "match_condition": False, # Snowflake
703
- "directed": False, # Snowflake
702
+ "match_condition": False,
703
+ "nearest": False,
704
+ "directed": False,
704
705
  "expressions": False,
705
706
  "pivots": False,
706
707
  }
@@ -817,6 +818,10 @@ class Join(Expression):
817
818
  return join
818
819
 
819
820
 
821
+ class Nearest(Expression):
822
+ arg_types = {"this": True, "count": True, "approximate": False, "similarity": False}
823
+
824
+
820
825
  class Lateral(Expression, UDTF):
821
826
  arg_types = {
822
827
  "this": True,
@@ -1938,6 +1943,10 @@ class WindowSpec(Expression):
1938
1943
  }
1939
1944
 
1940
1945
 
1946
+ class PartitionByExcluding(Expression):
1947
+ arg_types = {"expressions": True}
1948
+
1949
+
1941
1950
  class PreWhere(Expression):
1942
1951
  pass
1943
1952
 
@@ -2020,6 +2029,10 @@ class DropPartition(Expression):
2020
2029
  arg_types = {"expressions": True, "exists": False}
2021
2030
 
2022
2031
 
2032
+ class ReplaceColumns(Expression):
2033
+ arg_types = {"expressions": True}
2034
+
2035
+
2023
2036
  class ReplacePartition(Expression):
2024
2037
  arg_types = {"expression": True, "source": True}
2025
2038
 
@@ -333,6 +333,9 @@ class Generator:
333
333
  # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
334
334
  SUPPORTS_MERGE_WHERE = False
335
335
 
336
+ # Whether MERGE uses ALL BY NAME instead of a star to update or insert all columns
337
+ MERGE_ALL_BY_NAME = False
338
+
336
339
  # Whether the INTERVAL expression works only with values like '1 day'
337
340
  SINGLE_STRING_INTERVAL = False
338
341
 
@@ -370,6 +373,9 @@ class Generator:
370
373
  # Whether directed joins are supported
371
374
  DIRECTED_JOINS = False
372
375
 
376
+ # Whether top-k distance/similarity joins are supported
377
+ SUPPORTS_NEAREST_JOIN = False
378
+
373
379
  # Whether table hints should be generated
374
380
  TABLE_HINTS = True
375
381
 
@@ -414,9 +420,12 @@ class Generator:
414
420
  # SELECT * VALUES into SELECT UNION
415
421
  VALUES_AS_TABLE = True
416
422
 
417
- # Whether the word COLUMN is included when adding a column with ALTER TABLE
423
+ # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE.
418
424
  ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
419
425
 
426
+ # Whether ALTER TABLE mutli-column additions are wrapped in parentheses.
427
+ ALTER_TABLE_ADD_COLUMNS_WRAPPED = True
428
+
420
429
  # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
421
430
  UNNEST_WITH_ORDINALITY = True
422
431
 
@@ -1322,7 +1331,8 @@ class Generator:
1322
1331
  return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1323
1332
  options = self.expressions(expression, key="options", flat=True, sep=" ")
1324
1333
  options = f" {options}" if options else ""
1325
- return f"PRIMARY KEY{options}"
1334
+ include = self.sql(expression, "include")
1335
+ return f"PRIMARY KEY{include}{options}"
1326
1336
 
1327
1337
  def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1328
1338
  this = self.sql(expression, "this")
@@ -1334,7 +1344,8 @@ class Generator:
1334
1344
  nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1335
1345
  options = self.expressions(expression, key="options", flat=True, sep=" ")
1336
1346
  options = f" {options}" if options else ""
1337
- return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1347
+ include = self.sql(expression, "include")
1348
+ return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}"
1338
1349
 
1339
1350
  def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1340
1351
  input_ = expression.args.get("input_")
@@ -2024,7 +2035,9 @@ class Generator:
2024
2035
  with_storage = self.expressions(expression, key="with_storage", flat=True)
2025
2036
  with_storage = f" WITH ({with_storage})" if with_storage else ""
2026
2037
  tablespace = self.sql(expression, "tablespace")
2027
- tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
2038
+ if tablespace:
2039
+ prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX"
2040
+ tablespace = f"{prefix} TABLESPACE {tablespace}"
2028
2041
  on = self.sql(expression, "on")
2029
2042
  on = f" ON {on}" if on else ""
2030
2043
 
@@ -2168,7 +2181,7 @@ class Generator:
2168
2181
  def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2169
2182
  if isinstance(expression.this, exp.Dot):
2170
2183
  return self.sql(expression, "this")
2171
- return f"'{expression.name}'" if string_key else expression.name
2184
+ return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name
2172
2185
 
2173
2186
  def property_sql(self, expression: exp.Property) -> str:
2174
2187
  property_cls = expression.__class__
@@ -2178,6 +2191,7 @@ class Generator:
2178
2191
  property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2179
2192
  if not property_name:
2180
2193
  self.unsupported(f"Unsupported property {expression.key}")
2194
+ return ""
2181
2195
 
2182
2196
  return f"{property_name}={self.sql(expression, 'this')}"
2183
2197
 
@@ -2319,6 +2333,10 @@ class Generator:
2319
2333
  override = " OVERRIDE" if expression.args.get("override") else ""
2320
2334
  return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2321
2335
 
2336
+ def lockingstatement_sql(self, expression: exp.LockingStatement) -> str:
2337
+ self.unsupported("LOCKING request modifier is not supported")
2338
+ return self.sql(expression, "expression")
2339
+
2322
2340
  def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2323
2341
  data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2324
2342
  statistics = expression.args.get("statistics")
@@ -2561,6 +2579,10 @@ class Generator:
2561
2579
  expression: exp.TableSample,
2562
2580
  tablesample_keyword: str | None = None,
2563
2581
  ) -> str:
2582
+ if expression.expressions:
2583
+ self.unsupported("Multiple sample sets are not supported")
2584
+ return ""
2585
+
2564
2586
  method = self.sql(expression, "method")
2565
2587
  method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2566
2588
  numerator = self.sql(expression, "bucket_numerator")
@@ -2902,6 +2924,12 @@ class Generator:
2902
2924
  this = self.indent(self.sql(expression, "this"))
2903
2925
  return f"{self.seg('HAVING')}{self.sep()}{this}"
2904
2926
 
2927
+ def connectto_sql(self, expression: exp.ConnectTo) -> str:
2928
+ return f"CONNECT {self.sql(expression, 'this')}"
2929
+
2930
+ def disconnect_sql(self, expression: exp.Disconnect) -> str:
2931
+ return "DISCONNECT"
2932
+
2905
2933
  def connect_sql(self, expression: exp.Connect) -> str:
2906
2934
  start = self.sql(expression, "start")
2907
2935
  start = self.seg(f"START WITH {start}") if start else ""
@@ -2913,7 +2941,7 @@ class Generator:
2913
2941
  def prior_sql(self, expression: exp.Prior) -> str:
2914
2942
  return f"PRIOR {self.sql(expression, 'this')}"
2915
2943
 
2916
- def join_sql(self, expression: exp.Join) -> str:
2944
+ def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str:
2917
2945
  if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2918
2946
  side = None
2919
2947
  else:
@@ -2933,6 +2961,8 @@ class Generator:
2933
2961
  )
2934
2962
  match_cond = self.sql(expression, "match_condition")
2935
2963
  match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2964
+ nearest = self.sql(expression, "nearest")
2965
+ nearest = f" {nearest}" if nearest else ""
2936
2966
  on_sql = self.sql(expression, "on")
2937
2967
  using = expression.args.get("using")
2938
2968
 
@@ -2953,17 +2983,27 @@ class Generator:
2953
2983
  on_sql = f"{space}USING ({on_sql})"
2954
2984
  else:
2955
2985
  on_sql = f"{space}ON {on_sql}"
2956
- elif not op_sql:
2986
+ elif not op_sql and not nearest:
2957
2987
  if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2958
2988
  return f" {this_sql}"
2959
2989
 
2960
2990
  return f", {this_sql}"
2961
2991
 
2962
- if op_sql != "STRAIGHT_JOIN":
2992
+ if join_op:
2993
+ op_sql = join_op
2994
+ elif op_sql != "STRAIGHT_JOIN":
2963
2995
  op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2964
2996
 
2965
2997
  pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2966
- return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2998
+ return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}"
2999
+
3000
+ def nearest_sql(self, expression: exp.Nearest) -> str:
3001
+ if not self.SUPPORTS_NEAREST_JOIN:
3002
+ self.unsupported("NEAREST joins are not supported")
3003
+
3004
+ approximate = "APPROX " if expression.args.get("approximate") else ""
3005
+ metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE"
3006
+ return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}"
2967
3007
 
2968
3008
  def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2969
3009
  args = self.expressions(expression, flat=True)
@@ -3627,6 +3667,9 @@ class Generator:
3627
3667
  first = "FIRST" if first else "LAST"
3628
3668
 
3629
3669
  if not partition and not order and not spec and alias:
3670
+ # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required.
3671
+ if expression.arg_key == "windows":
3672
+ return f"{this} ({alias})"
3630
3673
  return f"{this} {alias}"
3631
3674
 
3632
3675
  args = self.format_args(
@@ -3638,6 +3681,9 @@ class Generator:
3638
3681
  partition = self.expressions(expression, key="partition_by", flat=True)
3639
3682
  return f"PARTITION BY {partition}" if partition else ""
3640
3683
 
3684
+ def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str:
3685
+ return f"EXCLUDING {self.expressions(expression, flat=True)}"
3686
+
3641
3687
  def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3642
3688
  kind = self.sql(expression, "kind")
3643
3689
  start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
@@ -4073,8 +4119,8 @@ class Generator:
4073
4119
  if this:
4074
4120
  interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4075
4121
  if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4076
- return f"{interval_keyword}'{this}'{unit}"
4077
- return f"{interval_keyword}'{this}{unit}'"
4122
+ return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}"
4123
+ return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}"
4078
4124
  return f"{interval_keyword}{unit}"
4079
4125
 
4080
4126
  this = self.sql(expression, "this")
@@ -4496,13 +4542,17 @@ class Generator:
4496
4542
  return f"{keyword} {items_sql}"
4497
4543
 
4498
4544
  def add_column_sql(self, expression: exp.Expr) -> str:
4499
- sql = self.sql(expression)
4500
- if isinstance(expression, exp.Schema):
4501
- column_text = " COLUMNS"
4502
- elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4503
- column_text = " COLUMN"
4545
+ column_text = ""
4546
+ if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4547
+ if isinstance(expression, exp.ColumnDef):
4548
+ column_text = " COLUMN"
4549
+ elif isinstance(expression, exp.Schema):
4550
+ column_text = " COLUMNS"
4551
+
4552
+ if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED:
4553
+ sql = self.expressions(expression, flat=True)
4504
4554
  else:
4505
- column_text = ""
4555
+ sql = self.sql(expression)
4506
4556
 
4507
4557
  return f"ADD{column_text} {sql}"
4508
4558
 
@@ -4517,6 +4567,9 @@ class Generator:
4517
4567
  def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4518
4568
  return f"ADD {self.expressions(expression, indent=False)}"
4519
4569
 
4570
+ def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str:
4571
+ return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})"
4572
+
4520
4573
  def addpartition_sql(self, expression: exp.AddPartition) -> str:
4521
4574
  exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4522
4575
  location = self.sql(expression, "location")
@@ -4771,14 +4824,22 @@ class Generator:
4771
4824
  sqls: list[str] = []
4772
4825
  stack: list[None | str | exp.Expr] = [expression]
4773
4826
  binary_type = type(expression)
4827
+ is_is = binary_type is exp.Is and op in ("IS", "IS NOT")
4828
+ negate = expression.args.get("negate")
4774
4829
 
4775
4830
  while stack:
4776
4831
  node = stack.pop()
4777
4832
 
4778
- if type(node) is binary_type:
4833
+ if type(node) is binary_type and (
4834
+ node is expression
4835
+ or (is_is and not isinstance(node.expression, exp.Boolean))
4836
+ or node.args.get("negate") == negate
4837
+ ):
4779
4838
  op_func = node.args.get("operator")
4780
4839
  if op_func:
4781
4840
  op = f"OPERATOR({self.sql(op_func)})"
4841
+ elif is_is:
4842
+ op = "IS NOT" if node.args.get("negate") else "IS"
4782
4843
 
4783
4844
  stack.append(node.args.get("expression"))
4784
4845
  stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
@@ -4957,13 +5018,27 @@ class Generator:
4957
5018
 
4958
5019
  then_expression = expression.args.get("then")
4959
5020
  if isinstance(then_expression, exp.Insert):
4960
- this = self.sql(then_expression, "this")
5021
+ this = (
5022
+ "ALL BY NAME"
5023
+ if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star)
5024
+ else self.sql(then_expression, "this")
5025
+ )
4961
5026
  this = f"INSERT {this}" if this else "INSERT"
4962
5027
  then = self.sql(then_expression, "expression")
4963
5028
  then = f"{this} VALUES {then}" if then else this
4964
5029
  elif isinstance(then_expression, exp.Update):
4965
5030
  if isinstance(then_expression.args.get("expressions"), exp.Star):
4966
- then = f"UPDATE {self.sql(then_expression, 'expressions')}"
5031
+ then = (
5032
+ "UPDATE ALL BY NAME"
5033
+ if self.MERGE_ALL_BY_NAME
5034
+ else f"UPDATE {self.sql(then_expression, 'expressions')}"
5035
+ )
5036
+ elif (
5037
+ self.MERGE_ALL_BY_NAME
5038
+ and len(then_expression.expressions) == 1
5039
+ and isinstance(then_expression.expressions[0], exp.Star)
5040
+ ):
5041
+ then = "UPDATE ALL BY NAME"
4967
5042
  else:
4968
5043
  expressions_sql = self.expressions(then_expression)
4969
5044
  then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
@@ -5924,7 +5999,8 @@ class Generator:
5924
5999
 
5925
6000
  def slice_sql(self, expression: exp.Slice) -> str:
5926
6001
  step = self.sql(expression, "step")
5927
- end = self.sql(expression.expression)
6002
+ # An omitted end is written as `-` before a step, since `::` is the cast operator
6003
+ end = self.sql(expression.expression) or ("-" if step else "")
5928
6004
  begin = self.sql(expression.this)
5929
6005
 
5930
6006
  sql = f"{end}:{step}" if step else end
@@ -6053,6 +6129,13 @@ class Generator:
6053
6129
  exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6054
6130
  )
6055
6131
 
6132
+ def arraysort_sql(self, expression: exp.ArraySort) -> str:
6133
+ asc = expression.args.get("asc")
6134
+ nulls_first = expression.args.get("nulls_first")
6135
+ if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()):
6136
+ self.unsupported("ARRAY_SORT only sorts ascending with NULLs last")
6137
+ return self.func("ARRAY_SORT", expression.this, expression.expression)
6138
+
6056
6139
  def arraysize_sql(self, expression: exp.ArraySize) -> str:
6057
6140
  dim = expression.expression
6058
6141