sqlglot 28.4.1__py3-none-any.whl → 28.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. sqlglot/_version.py +2 -2
  2. sqlglot/dialects/bigquery.py +20 -23
  3. sqlglot/dialects/clickhouse.py +2 -0
  4. sqlglot/dialects/dialect.py +355 -18
  5. sqlglot/dialects/doris.py +38 -90
  6. sqlglot/dialects/druid.py +1 -0
  7. sqlglot/dialects/duckdb.py +1739 -163
  8. sqlglot/dialects/exasol.py +17 -1
  9. sqlglot/dialects/hive.py +27 -2
  10. sqlglot/dialects/mysql.py +103 -11
  11. sqlglot/dialects/oracle.py +38 -1
  12. sqlglot/dialects/postgres.py +142 -33
  13. sqlglot/dialects/presto.py +6 -2
  14. sqlglot/dialects/redshift.py +7 -1
  15. sqlglot/dialects/singlestore.py +13 -3
  16. sqlglot/dialects/snowflake.py +271 -21
  17. sqlglot/dialects/spark.py +25 -0
  18. sqlglot/dialects/spark2.py +4 -3
  19. sqlglot/dialects/starrocks.py +152 -17
  20. sqlglot/dialects/trino.py +1 -0
  21. sqlglot/dialects/tsql.py +5 -0
  22. sqlglot/diff.py +1 -1
  23. sqlglot/expressions.py +239 -47
  24. sqlglot/generator.py +173 -44
  25. sqlglot/optimizer/annotate_types.py +129 -60
  26. sqlglot/optimizer/merge_subqueries.py +13 -2
  27. sqlglot/optimizer/qualify_columns.py +7 -0
  28. sqlglot/optimizer/resolver.py +19 -0
  29. sqlglot/optimizer/scope.py +12 -0
  30. sqlglot/optimizer/unnest_subqueries.py +7 -0
  31. sqlglot/parser.py +251 -58
  32. sqlglot/schema.py +186 -14
  33. sqlglot/tokens.py +36 -6
  34. sqlglot/transforms.py +6 -5
  35. sqlglot/typing/__init__.py +29 -10
  36. sqlglot/typing/bigquery.py +5 -10
  37. sqlglot/typing/duckdb.py +39 -0
  38. sqlglot/typing/hive.py +50 -1
  39. sqlglot/typing/mysql.py +32 -0
  40. sqlglot/typing/presto.py +0 -1
  41. sqlglot/typing/snowflake.py +80 -17
  42. sqlglot/typing/spark.py +29 -0
  43. sqlglot/typing/spark2.py +9 -1
  44. sqlglot/typing/tsql.py +21 -0
  45. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/METADATA +47 -2
  46. sqlglot-28.8.0.dist-info/RECORD +95 -0
  47. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/WHEEL +1 -1
  48. sqlglot-28.4.1.dist-info/RECORD +0 -92
  49. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/licenses/LICENSE +0 -0
  50. {sqlglot-28.4.1.dist-info → sqlglot-28.8.0.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
- from sqlglot import exp
5
+ from sqlglot import exp, transforms
6
6
  from sqlglot.dialects.dialect import (
7
7
  approx_count_distinct_sql,
8
8
  arrow_json_extract_sql,
@@ -17,6 +17,31 @@ from sqlglot.helper import seq_get
17
17
  from sqlglot.tokens import TokenType
18
18
 
19
19
 
20
+ def _eliminate_between_in_delete(expression: exp.Expression) -> exp.Expression:
21
+ """
22
+ StarRocks doesn't support BETWEEN in DELETE statements, so we convert
23
+ BETWEEN expressions to explicit comparisons.
24
+
25
+ https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/DELETE/#parameters
26
+
27
+ Example:
28
+ >>> from sqlglot import parse_one
29
+ >>> expr = parse_one("DELETE FROM t WHERE x BETWEEN 1 AND 10")
30
+ >>> print(_eliminate_between_in_delete(expr).sql(dialect="starrocks"))
31
+ DELETE FROM t WHERE x >= 1 AND x <= 10
32
+ """
33
+ if where := expression.args.get("where"):
34
+ for between in where.find_all(exp.Between):
35
+ between.replace(
36
+ exp.and_(
37
+ exp.GTE(this=between.this.copy(), expression=between.args["low"]),
38
+ exp.LTE(this=between.this.copy(), expression=between.args["high"]),
39
+ copy=False,
40
+ )
41
+ )
42
+ return expression
43
+
44
+
20
45
  # https://docs.starrocks.io/docs/sql-reference/sql-functions/spatial-functions/st_distance_sphere/
21
46
  def st_distance_sphere(self, expression: exp.StDistance) -> str:
22
47
  point1 = expression.this
@@ -56,11 +81,31 @@ class StarRocks(MySQL):
56
81
 
57
82
  PROPERTY_PARSERS = {
58
83
  **MySQL.Parser.PROPERTY_PARSERS,
59
- "PARTITION BY": lambda self: self._parse_partition_by_opt_range(),
60
84
  "PROPERTIES": lambda self: self._parse_wrapped_properties(),
61
85
  "UNIQUE": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),
86
+ "ROLLUP": lambda self: self._parse_rollup_property(),
87
+ "REFRESH": lambda self: self._parse_refresh_property(),
62
88
  }
63
89
 
90
+ def _parse_rollup_property(self) -> exp.RollupProperty:
91
+ # ROLLUP (rollup_name (col1, col2) [FROM from_index] [PROPERTIES (...)], ...)
92
+ def parse_rollup_index() -> exp.RollupIndex:
93
+ return self.expression(
94
+ exp.RollupIndex,
95
+ this=self._parse_id_var(),
96
+ expressions=self._parse_wrapped_id_vars(),
97
+ from_index=self._parse_id_var() if self._match_text_seq("FROM") else None,
98
+ properties=self.expression(
99
+ exp.Properties, expressions=self._parse_wrapped_properties()
100
+ )
101
+ if self._match_text_seq("PROPERTIES")
102
+ else None,
103
+ )
104
+
105
+ return self.expression(
106
+ exp.RollupProperty, expressions=self._parse_wrapped_csv(parse_rollup_index)
107
+ )
108
+
64
109
  def _parse_create(self) -> exp.Create | exp.Command:
65
110
  create = super()._parse_create()
66
111
 
@@ -94,6 +139,40 @@ class StarRocks(MySQL):
94
139
 
95
140
  return unnest
96
141
 
142
+ def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
143
+ return self.expression(
144
+ exp.PartitionedByProperty,
145
+ this=exp.Schema(
146
+ expressions=self._parse_wrapped_csv(self._parse_assignment, optional=True)
147
+ ),
148
+ )
149
+
150
+ def _parse_partition_property(
151
+ self,
152
+ ) -> t.Optional[exp.Expression] | t.List[exp.Expression]:
153
+ expr = super()._parse_partition_property()
154
+
155
+ if not expr:
156
+ return self._parse_partitioned_by()
157
+
158
+ if isinstance(expr, exp.Property):
159
+ return expr
160
+
161
+ self._match_l_paren()
162
+
163
+ if self._match_text_seq("START", advance=False):
164
+ create_expressions = self._parse_csv(self._parse_partitioning_granularity_dynamic)
165
+ else:
166
+ create_expressions = None
167
+
168
+ self._match_r_paren()
169
+
170
+ return self.expression(
171
+ exp.PartitionByRangeProperty,
172
+ partition_expressions=expr,
173
+ create_expressions=create_expressions,
174
+ )
175
+
97
176
  def _parse_partitioning_granularity_dynamic(self) -> exp.PartitionByRangePropertyDynamic:
98
177
  self._match_text_seq("START")
99
178
  start = self._parse_wrapped(self._parse_string)
@@ -105,20 +184,33 @@ class StarRocks(MySQL):
105
184
  exp.PartitionByRangePropertyDynamic, start=start, end=end, every=every
106
185
  )
107
186
 
108
- def _parse_partition_by_opt_range(
109
- self,
110
- ) -> exp.PartitionedByProperty | exp.PartitionByRangeProperty:
111
- if self._match_text_seq("RANGE"):
112
- partition_expressions = self._parse_wrapped_id_vars()
113
- create_expressions = self._parse_wrapped_csv(
114
- self._parse_partitioning_granularity_dynamic
115
- )
116
- return self.expression(
117
- exp.PartitionByRangeProperty,
118
- partition_expressions=partition_expressions,
119
- create_expressions=create_expressions,
120
- )
121
- return super()._parse_partitioned_by()
187
+ def _parse_refresh_property(self) -> exp.RefreshTriggerProperty:
188
+ """
189
+ REFRESH [DEFERRED | IMMEDIATE]
190
+ [ASYNC | ASYNC [START (<start_time>)] EVERY (INTERVAL <refresh_interval>) | MANUAL]
191
+ """
192
+ method = self._match_texts(("DEFERRED", "IMMEDIATE")) and self._prev.text.upper()
193
+ kind = self._match_texts(("ASYNC", "MANUAL")) and self._prev.text.upper()
194
+ start = self._match_text_seq("START") and self._parse_wrapped(self._parse_string)
195
+
196
+ if self._match_text_seq("EVERY"):
197
+ self._match_l_paren()
198
+ self._match_text_seq("INTERVAL")
199
+ every = self._parse_number()
200
+ unit = self._parse_var(any_token=True)
201
+ self._match_r_paren()
202
+ else:
203
+ every = None
204
+ unit = None
205
+
206
+ return self.expression(
207
+ exp.RefreshTriggerProperty,
208
+ method=method,
209
+ kind=kind,
210
+ starts=start,
211
+ every=every,
212
+ unit=unit,
213
+ )
122
214
 
123
215
  class Generator(MySQL.Generator):
124
216
  EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
@@ -126,9 +218,13 @@ class StarRocks(MySQL):
126
218
  VARCHAR_REQUIRES_SIZE = False
127
219
  PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
128
220
  WITH_PROPERTIES_PREFIX = "PROPERTIES"
221
+ UPDATE_STATEMENT_SUPPORTS_FROM = True
222
+ INSERT_OVERWRITE = " OVERWRITE"
129
223
 
130
224
  # StarRocks doesn't support "IS TRUE/FALSE" syntax.
131
225
  IS_BOOL_ALLOWED = False
226
+ # StarRocks doesn't support renaming a table with a database.
227
+ RENAME_TABLE_WITH_DB = False
132
228
 
133
229
  CAST_MAPPING = {}
134
230
 
@@ -144,7 +240,8 @@ class StarRocks(MySQL):
144
240
  **MySQL.Generator.PROPERTIES_LOCATION,
145
241
  exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
146
242
  exp.UniqueKeyProperty: exp.Properties.Location.POST_SCHEMA,
147
- exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA,
243
+ exp.RollupProperty: exp.Properties.Location.POST_SCHEMA,
244
+ exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
148
245
  }
149
246
 
150
247
  TRANSFORMS = {
@@ -157,6 +254,7 @@ class StarRocks(MySQL):
157
254
  exp.DateDiff: lambda self, e: self.func(
158
255
  "DATE_DIFF", unit_to_str(e), e.this, e.expression
159
256
  ),
257
+ exp.Delete: transforms.preprocess([_eliminate_between_in_delete]),
160
258
  exp.Flatten: rename_func("ARRAY_FLATTEN"),
161
259
  exp.JSONExtractScalar: arrow_json_extract_sql,
162
260
  exp.JSONExtract: arrow_json_extract_sql,
@@ -348,3 +446,40 @@ class StarRocks(MySQL):
348
446
  props.set("expressions", primary_key.pop(), engine_index + 1, overwrite=False)
349
447
 
350
448
  return super().create_sql(expression)
449
+
450
+ def partitionedbyproperty_sql(self, expression: exp.PartitionedByProperty) -> str:
451
+ this = expression.this
452
+ if isinstance(this, exp.Schema):
453
+ # For MVs, StarRocks needs outer parentheses.
454
+ create = expression.find_ancestor(exp.Create)
455
+
456
+ sql = self.expressions(this, flat=True)
457
+ if (create and create.kind == "VIEW") or all(
458
+ isinstance(col, (exp.Column, exp.Identifier)) for col in this.expressions
459
+ ):
460
+ sql = f"({sql})"
461
+
462
+ return f"PARTITION BY {sql}"
463
+
464
+ return f"PARTITION BY {self.sql(this)}"
465
+
466
+ def cluster_sql(self, expression: exp.Cluster) -> str:
467
+ """Generate StarRocks ORDER BY clause for clustering."""
468
+ expressions = self.expressions(expression, flat=True)
469
+ return f"ORDER BY ({expressions})" if expressions else ""
470
+
471
+ def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
472
+ """Generate StarRocks REFRESH clause for materialized views.
473
+ There is a little difference of the syntax between StarRocks and Doris.
474
+ """
475
+ method = self.sql(expression, "method")
476
+ method = f" {method}" if method else ""
477
+ kind = self.sql(expression, "kind")
478
+ kind = f" {kind}" if kind else ""
479
+ starts = self.sql(expression, "starts")
480
+ starts = f" START ({starts})" if starts else ""
481
+ every = self.sql(expression, "every")
482
+ unit = self.sql(expression, "unit")
483
+ every = f" EVERY (INTERVAL {every} {unit})" if every and unit else ""
484
+
485
+ return f"REFRESH{method}{kind}{starts}{every}"
sqlglot/dialects/trino.py CHANGED
@@ -69,6 +69,7 @@ class Trino(Presto):
69
69
  )
70
70
 
71
71
  class Generator(Presto.Generator):
72
+ EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
72
73
  PROPERTIES_LOCATION = {
73
74
  **Presto.Generator.PROPERTIES_LOCATION,
74
75
  exp.LocationProperty: exp.Properties.Location.POST_WITH,
sqlglot/dialects/tsql.py CHANGED
@@ -601,6 +601,7 @@ class TSQL(Dialect):
601
601
 
602
602
  FUNCTIONS = {
603
603
  **parser.Parser.FUNCTIONS,
604
+ "ATN2": exp.Atan2.from_arg_list,
604
605
  "CHARINDEX": lambda args: exp.StrPosition(
605
606
  this=seq_get(args, 1),
606
607
  substr=seq_get(args, 0),
@@ -989,6 +990,9 @@ class TSQL(Dialect):
989
990
 
990
991
  return expression
991
992
 
993
+ def _parse_primary_key_part(self) -> t.Optional[exp.Expression]:
994
+ return self._parse_ordered()
995
+
992
996
  class Generator(generator.Generator):
993
997
  LIMIT_IS_TOP = True
994
998
  QUERY_HINTS = False
@@ -1055,6 +1059,7 @@ class TSQL(Dialect):
1055
1059
  TRANSFORMS = {
1056
1060
  **generator.Generator.TRANSFORMS,
1057
1061
  exp.AnyValue: any_value_to_max_sql,
1062
+ exp.Atan2: rename_func("ATN2"),
1058
1063
  exp.ArrayToString: rename_func("STRING_AGG"),
1059
1064
  exp.AutoIncrementColumnConstraint: lambda *_: "IDENTITY",
1060
1065
  exp.Ceil: rename_func("CEILING"),
sqlglot/diff.py CHANGED
@@ -181,7 +181,7 @@ class ChangeDistiller:
181
181
  def __init__(self, f: float = 0.6, t: float = 0.6, dialect: DialectType = None) -> None:
182
182
  self.f = f
183
183
  self.t = t
184
- self._sql_generator = Dialect.get_or_raise(dialect).generator()
184
+ self._sql_generator = Dialect.get_or_raise(dialect).generator(comments=False)
185
185
 
186
186
  def diff(
187
187
  self,