sqlglot 27.20.0__py3-none-any.whl → 27.22.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.
sqlglot/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '27.20.0'
32
- __version_tuple__ = version_tuple = (27, 20, 0)
31
+ __version__ = version = '27.22.0'
32
+ __version_tuple__ = version_tuple = (27, 22, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -867,6 +867,8 @@ class BigQuery(Dialect):
867
867
  "FROM_HEX": exp.Unhex.from_arg_list,
868
868
  "WEEK": lambda args: exp.WeekStart(this=exp.var(seq_get(args, 0))),
869
869
  }
870
+ # Remove SEARCH to avoid parameter routing issues - let it fall back to Anonymous function
871
+ FUNCTIONS.pop("SEARCH")
870
872
 
871
873
  FUNCTION_PARSERS = {
872
874
  **parser.Parser.FUNCTION_PARSERS,
@@ -525,6 +525,13 @@ class Dialect(metaclass=_Dialect):
525
525
  equivalent of CREATE SCHEMA is CREATE DATABASE.
526
526
  """
527
527
 
528
+ ALTER_TABLE_SUPPORTS_CASCADE = False
529
+ """
530
+ Hive by default does not update the schema of existing partitions when a column is changed.
531
+ the CASCADE clause is used to indicate that the change should be propagated to all existing partitions.
532
+ the Spark dialect, while derived from Hive, does not support the CASCADE clause.
533
+ """
534
+
528
535
  # Whether ADD is present for each column added by ALTER TABLE
529
536
  ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = True
530
537
 
@@ -1715,7 +1722,7 @@ def unit_to_str(expression: exp.Expression, default: str = "DAY") -> t.Optional[
1715
1722
  def unit_to_var(expression: exp.Expression, default: str = "DAY") -> t.Optional[exp.Expression]:
1716
1723
  unit = expression.args.get("unit")
1717
1724
 
1718
- if isinstance(unit, (exp.Var, exp.Placeholder, exp.WeekStart)):
1725
+ if isinstance(unit, (exp.Var, exp.Placeholder, exp.WeekStart, exp.Column)):
1719
1726
  return unit
1720
1727
 
1721
1728
  value = unit.name if unit else default
@@ -1736,7 +1743,9 @@ def map_date_part(
1736
1743
 
1737
1744
  def map_date_part(part, dialect: DialectType = Dialect):
1738
1745
  mapped = (
1739
- Dialect.get_or_raise(dialect).DATE_PART_MAPPING.get(part.name.upper()) if part else None
1746
+ Dialect.get_or_raise(dialect).DATE_PART_MAPPING.get(part.name.upper())
1747
+ if part and not (isinstance(part, exp.Column) and len(part.parts) != 1)
1748
+ else None
1740
1749
  )
1741
1750
  if mapped:
1742
1751
  return exp.Literal.string(mapped) if part.is_string else exp.var(mapped)
@@ -311,6 +311,7 @@ class DuckDB(Dialect):
311
311
  "PIVOT_WIDER": TokenType.PIVOT,
312
312
  "POSITIONAL": TokenType.POSITIONAL,
313
313
  "RESET": TokenType.COMMAND,
314
+ "ROW": TokenType.STRUCT,
314
315
  "SIGNED": TokenType.INT,
315
316
  "STRING": TokenType.TEXT,
316
317
  "SUMMARIZE": TokenType.SUMMARIZE,
@@ -337,16 +338,14 @@ class DuckDB(Dialect):
337
338
  class Parser(parser.Parser):
338
339
  MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS = True
339
340
 
340
- BITWISE = {
341
- **parser.Parser.BITWISE,
342
- TokenType.TILDA: exp.RegexpLike,
343
- }
341
+ BITWISE = parser.Parser.BITWISE.copy()
344
342
  BITWISE.pop(TokenType.CARET)
345
343
 
346
344
  RANGE_PARSERS = {
347
345
  **parser.Parser.RANGE_PARSERS,
348
346
  TokenType.DAMP: binary_range_parser(exp.ArrayOverlaps),
349
347
  TokenType.CARET_AT: binary_range_parser(exp.StartsWith),
348
+ TokenType.TILDA: binary_range_parser(exp.RegexpFullMatch),
350
349
  }
351
350
 
352
351
  EXPONENT = {
sqlglot/dialects/hive.py CHANGED
@@ -211,6 +211,7 @@ class Hive(Dialect):
211
211
  SAFE_DIVISION = True
212
212
  ARRAY_AGG_INCLUDES_NULLS = None
213
213
  REGEXP_EXTRACT_DEFAULT_GROUP = 1
214
+ ALTER_TABLE_SUPPORTS_CASCADE = True
214
215
 
215
216
  # https://spark.apache.org/docs/latest/sql-ref-identifier.html#description
216
217
  NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE
@@ -310,6 +311,10 @@ class Hive(Dialect):
310
311
  VALUES_FOLLOWED_BY_PAREN = False
311
312
  JOINS_HAVE_EQUAL_PRECEDENCE = True
312
313
  ADD_JOIN_ON_TRUE = True
314
+ ALTER_TABLE_PARTITIONS = True
315
+
316
+ CHANGE_COLUMN_ALTER_SYNTAX = False
317
+ # Whether the dialect supports using ALTER COLUMN syntax with CHANGE COLUMN.
313
318
 
314
319
  FUNCTIONS = {
315
320
  **parser.Parser.FUNCTIONS,
@@ -378,6 +383,11 @@ class Hive(Dialect):
378
383
  ),
379
384
  }
380
385
 
386
+ ALTER_PARSERS = {
387
+ **parser.Parser.ALTER_PARSERS,
388
+ "CHANGE": lambda self: self._parse_alter_table_change(),
389
+ }
390
+
381
391
  def _parse_transform(self) -> t.Optional[exp.Transform | exp.QueryTransform]:
382
392
  if not self._match(TokenType.L_PAREN, advance=False):
383
393
  self._retreat(self._index - 1)
@@ -451,6 +461,35 @@ class Hive(Dialect):
451
461
 
452
462
  return this
453
463
 
464
+ def _parse_alter_table_change(self) -> t.Optional[exp.Expression]:
465
+ self._match(TokenType.COLUMN)
466
+ this = self._parse_field(any_token=True)
467
+
468
+ if self.CHANGE_COLUMN_ALTER_SYNTAX and self._match_text_seq("TYPE"):
469
+ return self.expression(
470
+ exp.AlterColumn,
471
+ this=this,
472
+ dtype=self._parse_types(schema=True),
473
+ )
474
+
475
+ column_new = self._parse_field(any_token=True)
476
+ dtype = self._parse_types(schema=True)
477
+
478
+ comment = self._match(TokenType.COMMENT) and self._parse_string()
479
+
480
+ if not this or not column_new or not dtype:
481
+ self.raise_error(
482
+ "Expected 'CHANGE COLUMN' to be followed by 'column_name' 'column_name' 'data_type'"
483
+ )
484
+
485
+ return self.expression(
486
+ exp.AlterColumn,
487
+ this=this,
488
+ rename_to=column_new,
489
+ dtype=dtype,
490
+ comment=comment,
491
+ )
492
+
454
493
  def _parse_partition_and_order(
455
494
  self,
456
495
  ) -> t.Tuple[t.List[exp.Expression], t.Optional[exp.Expression]]:
@@ -500,6 +539,7 @@ class Hive(Dialect):
500
539
  PAD_FILL_PATTERN_IS_REQUIRED = True
501
540
  SUPPORTS_MEDIAN = False
502
541
  ARRAY_SIZE_NAME = "SIZE"
542
+ ALTER_SET_TYPE = ""
503
543
 
504
544
  EXPRESSIONS_WITHOUT_NESTED_CTES = {
505
545
  exp.Insert,
@@ -531,7 +571,6 @@ class Hive(Dialect):
531
571
 
532
572
  TRANSFORMS = {
533
573
  **generator.Generator.TRANSFORMS,
534
- exp.Group: transforms.preprocess([transforms.unalias_group]),
535
574
  exp.Property: property_sql,
536
575
  exp.AnyValue: rename_func("FIRST"),
537
576
  exp.ApproxDistinct: approx_count_distinct_sql,
@@ -758,6 +797,32 @@ class Hive(Dialect):
758
797
  ),
759
798
  )
760
799
 
800
+ def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
801
+ this = self.sql(expression, "this")
802
+ new_name = self.sql(expression, "rename_to") or this
803
+ dtype = self.sql(expression, "dtype")
804
+ comment = (
805
+ f" COMMENT {self.sql(expression, 'comment')}"
806
+ if self.sql(expression, "comment")
807
+ else ""
808
+ )
809
+ default = self.sql(expression, "default")
810
+ visible = expression.args.get("visible")
811
+ allow_null = expression.args.get("allow_null")
812
+ drop = expression.args.get("drop")
813
+
814
+ if any([default, drop, visible, allow_null, drop]):
815
+ self.unsupported("Unsupported CHANGE COLUMN syntax")
816
+
817
+ if not dtype:
818
+ self.unsupported("CHANGE COLUMN without a type is not supported")
819
+
820
+ return f"CHANGE COLUMN {this} {new_name} {dtype}{comment}"
821
+
822
+ def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
823
+ self.unsupported("Cannot rename columns without data type defined in Hive")
824
+ return ""
825
+
761
826
  def alterset_sql(self, expression: exp.AlterSet) -> str:
762
827
  exprs = self.expressions(expression, flat=True)
763
828
  exprs = f" {exprs}" if exprs else ""
@@ -307,7 +307,6 @@ class Oracle(Dialect):
307
307
  ),
308
308
  exp.DateTrunc: lambda self, e: self.func("TRUNC", e.this, e.unit),
309
309
  exp.EuclideanDistance: rename_func("L2_DISTANCE"),
310
- exp.Group: transforms.preprocess([transforms.unalias_group]),
311
310
  exp.ILike: no_ilike_sql,
312
311
  exp.LogicalOr: rename_func("MAX"),
313
312
  exp.LogicalAnd: rename_func("MIN"),
@@ -475,7 +475,6 @@ class Presto(Dialect):
475
475
  e: f"WITH_TIMEZONE({self.sql(e, 'this')}, {self.sql(e, 'zone')}) AT TIME ZONE 'UTC'",
476
476
  exp.GenerateSeries: sequence_sql,
477
477
  exp.GenerateDateArray: sequence_sql,
478
- exp.Group: transforms.preprocess([transforms.unalias_group]),
479
478
  exp.If: if_sql(),
480
479
  exp.ILike: no_ilike_sql,
481
480
  exp.Initcap: _initcap_sql,
@@ -25,6 +25,20 @@ class RisingWave(Postgres):
25
25
  "KEY": lambda self: self._parse_encode_property(key=True),
26
26
  }
27
27
 
28
+ CONSTRAINT_PARSERS = {
29
+ **Postgres.Parser.CONSTRAINT_PARSERS,
30
+ "WATERMARK": lambda self: self.expression(
31
+ exp.WatermarkColumnConstraint,
32
+ this=self._match(TokenType.FOR) and self._parse_column(),
33
+ expression=self._match(TokenType.ALIAS) and self._parse_disjunction(),
34
+ ),
35
+ }
36
+
37
+ SCHEMA_UNNAMED_CONSTRAINTS = {
38
+ *Postgres.Parser.SCHEMA_UNNAMED_CONSTRAINTS,
39
+ "WATERMARK",
40
+ }
41
+
28
42
  def _parse_table_hints(self) -> t.Optional[t.List[exp.Expression]]:
29
43
  # There is no hint in risingwave.
30
44
  # Do nothing here to avoid WITH keywords conflict in CREATE SINK statement.
@@ -41,7 +41,18 @@ if t.TYPE_CHECKING:
41
41
  from sqlglot._typing import E, B
42
42
 
43
43
 
44
- # from https://docs.snowflake.com/en/sql-reference/functions/to_timestamp.html
44
+ def _build_strtok(args: t.List) -> exp.SplitPart:
45
+ # Add default delimiter (space) if missing - per Snowflake docs
46
+ if len(args) == 1:
47
+ args.append(exp.Literal.string(" "))
48
+
49
+ # Add default part_index (1) if missing
50
+ if len(args) == 2:
51
+ args.append(exp.Literal.number(1))
52
+
53
+ return exp.SplitPart.from_arg_list(args)
54
+
55
+
45
56
  def _build_datetime(
46
57
  name: str, kind: exp.DataType.Type, safe: bool = False
47
58
  ) -> t.Callable[[t.List], exp.Func]:
@@ -137,12 +148,35 @@ def _build_if_from_div0(args: t.List) -> exp.If:
137
148
  return exp.If(this=cond, true=true, false=false)
138
149
 
139
150
 
151
+ # https://docs.snowflake.com/en/sql-reference/functions/div0null
152
+ def _build_if_from_div0null(args: t.List) -> exp.If:
153
+ lhs = exp._wrap(seq_get(args, 0), exp.Binary)
154
+ rhs = exp._wrap(seq_get(args, 1), exp.Binary)
155
+
156
+ # Returns 0 when divisor is 0 OR NULL
157
+ cond = exp.EQ(this=rhs, expression=exp.Literal.number(0)).or_(
158
+ exp.Is(this=rhs, expression=exp.null())
159
+ )
160
+ true = exp.Literal.number(0)
161
+ false = exp.Div(this=lhs, expression=rhs)
162
+ return exp.If(this=cond, true=true, false=false)
163
+
164
+
140
165
  # https://docs.snowflake.com/en/sql-reference/functions/zeroifnull
141
166
  def _build_if_from_zeroifnull(args: t.List) -> exp.If:
142
167
  cond = exp.Is(this=seq_get(args, 0), expression=exp.Null())
143
168
  return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0))
144
169
 
145
170
 
171
+ def _build_search(args: t.List) -> exp.Search:
172
+ kwargs = {
173
+ "this": seq_get(args, 0),
174
+ "expression": seq_get(args, 1),
175
+ **{arg.name.lower(): arg for arg in args[2:] if isinstance(arg, exp.Kwarg)},
176
+ }
177
+ return exp.Search(**kwargs)
178
+
179
+
146
180
  # https://docs.snowflake.com/en/sql-reference/functions/zeroifnull
147
181
  def _build_if_from_nullifzero(args: t.List) -> exp.If:
148
182
  cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0))
@@ -529,6 +563,16 @@ class Snowflake(Dialect):
529
563
 
530
564
  TYPE_TO_EXPRESSIONS = {
531
565
  **Dialect.TYPE_TO_EXPRESSIONS,
566
+ exp.DataType.Type.DOUBLE: {
567
+ *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.DOUBLE],
568
+ exp.Cos,
569
+ exp.Cosh,
570
+ exp.Cot,
571
+ exp.Degrees,
572
+ exp.Exp,
573
+ exp.Sin,
574
+ exp.Tan,
575
+ },
532
576
  exp.DataType.Type.INT: {
533
577
  *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.INT],
534
578
  exp.Ascii,
@@ -539,6 +583,7 @@ class Snowflake(Dialect):
539
583
  exp.Levenshtein,
540
584
  exp.JarowinklerSimilarity,
541
585
  exp.StrPosition,
586
+ exp.Unicode,
542
587
  },
543
588
  exp.DataType.Type.VARCHAR: {
544
589
  *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.VARCHAR],
@@ -564,8 +609,10 @@ class Snowflake(Dialect):
564
609
  exp.SHA,
565
610
  exp.SHA2,
566
611
  exp.Soundex,
612
+ exp.SoundexP123,
567
613
  exp.Space,
568
614
  exp.SplitPart,
615
+ exp.Translate,
569
616
  exp.Uuid,
570
617
  },
571
618
  exp.DataType.Type.BINARY: {
@@ -587,6 +634,8 @@ class Snowflake(Dialect):
587
634
  },
588
635
  exp.DataType.Type.ARRAY: {
589
636
  exp.Split,
637
+ exp.RegexpExtractAll,
638
+ exp.StringToArray,
590
639
  },
591
640
  exp.DataType.Type.OBJECT: {
592
641
  exp.ParseUrl,
@@ -595,6 +644,10 @@ class Snowflake(Dialect):
595
644
  exp.DataType.Type.DECIMAL: {
596
645
  exp.RegexpCount,
597
646
  },
647
+ exp.DataType.Type.BOOLEAN: {
648
+ *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.BOOLEAN],
649
+ exp.Search,
650
+ },
598
651
  }
599
652
 
600
653
  ANNOTATORS = {
@@ -612,13 +665,20 @@ class Snowflake(Dialect):
612
665
  exp.Right,
613
666
  exp.Stuff,
614
667
  exp.Substring,
668
+ exp.Round,
669
+ )
670
+ },
671
+ **{
672
+ expr_type: lambda self, e: self._annotate_with_type(
673
+ e, exp.DataType.build("NUMBER", dialect="snowflake")
674
+ )
675
+ for expr_type in (
676
+ exp.RegexpCount,
677
+ exp.RegexpInstr,
615
678
  )
616
679
  },
617
680
  exp.ConcatWs: lambda self, e: self._annotate_by_args(e, "expressions"),
618
681
  exp.Reverse: _annotate_reverse,
619
- exp.RegexpCount: lambda self, e: self._annotate_with_type(
620
- e, exp.DataType.build("NUMBER", dialect="snowflake")
621
- ),
622
682
  }
623
683
 
624
684
  TIME_MAPPING = {
@@ -691,7 +751,7 @@ class Snowflake(Dialect):
691
751
  "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list,
692
752
  "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args),
693
753
  "ARRAY_CONTAINS": lambda args: exp.ArrayContains(
694
- this=seq_get(args, 1), expression=seq_get(args, 0)
754
+ this=seq_get(args, 1), expression=seq_get(args, 0), ensure_variant=False
695
755
  ),
696
756
  "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries(
697
757
  # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive
@@ -727,6 +787,7 @@ class Snowflake(Dialect):
727
787
  "DATEDIFF": _build_datediff,
728
788
  "DAYOFWEEKISO": exp.DayOfWeekIso.from_arg_list,
729
789
  "DIV0": _build_if_from_div0,
790
+ "DIV0NULL": _build_if_from_div0null,
730
791
  "EDITDISTANCE": lambda args: exp.Levenshtein(
731
792
  this=seq_get(args, 0), expression=seq_get(args, 1), max_dist=seq_get(args, 2)
732
793
  ),
@@ -765,6 +826,7 @@ class Snowflake(Dialect):
765
826
  "SHA2_BINARY": exp.SHA2Digest.from_arg_list,
766
827
  "SHA2_HEX": exp.SHA2.from_arg_list,
767
828
  "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)),
829
+ "STRTOK": _build_strtok,
768
830
  "TABLE": lambda args: exp.TableFromRows(this=seq_get(args, 0)),
769
831
  "TIMEADD": _build_date_time_add(exp.TimeAdd),
770
832
  "TIMEDIFF": _build_datediff,
@@ -799,6 +861,7 @@ class Snowflake(Dialect):
799
861
  "ZEROIFNULL": _build_if_from_zeroifnull,
800
862
  "LIKE": _build_like(exp.Like),
801
863
  "ILIKE": _build_like(exp.ILike),
864
+ "SEARCH": _build_search,
802
865
  }
803
866
  FUNCTIONS.pop("PREDICT")
804
867
 
@@ -1364,7 +1427,13 @@ class Snowflake(Dialect):
1364
1427
  exp.ArgMax: rename_func("MAX_BY"),
1365
1428
  exp.ArgMin: rename_func("MIN_BY"),
1366
1429
  exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"),
1367
- exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this),
1430
+ exp.ArrayContains: lambda self, e: self.func(
1431
+ "ARRAY_CONTAINS",
1432
+ e.expression
1433
+ if e.args.get("ensure_variant") is False
1434
+ else exp.cast(e.expression, exp.DataType.Type.VARIANT, copy=False),
1435
+ e.this,
1436
+ ),
1368
1437
  exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"),
1369
1438
  exp.AtTimeZone: lambda self, e: self.func(
1370
1439
  "CONVERT_TIMEZONE", e.args.get("zone"), e.this
@@ -1894,3 +1963,13 @@ class Snowflake(Dialect):
1894
1963
  return self.func("TO_CHAR", expression.expressions[0])
1895
1964
 
1896
1965
  return self.function_fallback_sql(expression)
1966
+
1967
+ def splitpart_sql(self, expression: exp.SplitPart) -> str:
1968
+ # Set part_index to 1 if missing
1969
+ if not expression.args.get("delimiter"):
1970
+ expression.set("delimiter", exp.Literal.string(" "))
1971
+
1972
+ if not expression.args.get("part_index"):
1973
+ expression.set("part_index", exp.Literal.number(1))
1974
+
1975
+ return rename_func("SPLIT_PART")(self, expression)
sqlglot/dialects/spark.py CHANGED
@@ -230,7 +230,6 @@ class Spark(Spark2):
230
230
  }
231
231
  TRANSFORMS.pop(exp.AnyValue)
232
232
  TRANSFORMS.pop(exp.DateDiff)
233
- TRANSFORMS.pop(exp.Group)
234
233
 
235
234
  def bracket_sql(self, expression: exp.Bracket) -> str:
236
235
  if expression.args.get("safe"):
@@ -151,6 +151,8 @@ def _annotate_by_similar_args(
151
151
 
152
152
 
153
153
  class Spark2(Hive):
154
+ ALTER_TABLE_SUPPORTS_CASCADE = False
155
+
154
156
  ANNOTATORS = {
155
157
  **Hive.ANNOTATORS,
156
158
  exp.Substring: lambda self, e: self._annotate_by_args(e, "this"),
@@ -172,6 +174,7 @@ class Spark2(Hive):
172
174
 
173
175
  class Parser(Hive.Parser):
174
176
  TRIM_PATTERN_FIRST = True
177
+ CHANGE_COLUMN_ALTER_SYNTAX = True
175
178
 
176
179
  FUNCTIONS = {
177
180
  **Hive.Parser.FUNCTIONS,
@@ -248,6 +251,7 @@ class Spark2(Hive):
248
251
  QUERY_HINTS = True
249
252
  NVL2_SUPPORTED = True
250
253
  CAN_IMPLEMENT_ARRAY_ANY = True
254
+ ALTER_SET_TYPE = "TYPE"
251
255
 
252
256
  PROPERTIES_LOCATION = {
253
257
  **Hive.Generator.PROPERTIES_LOCATION,
@@ -364,3 +368,16 @@ class Spark2(Hive):
364
368
  return super().fileformatproperty_sql(expression)
365
369
 
366
370
  return f"USING {expression.name.upper()}"
371
+
372
+ def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
373
+ this = self.sql(expression, "this")
374
+ new_name = self.sql(expression, "rename_to") or this
375
+ comment = self.sql(expression, "comment")
376
+ if new_name == this:
377
+ if comment:
378
+ return f"ALTER COLUMN {this} COMMENT {comment}"
379
+ return super(Hive.Generator, self).altercolumn_sql(expression)
380
+ return f"RENAME COLUMN {this} TO {new_name}"
381
+
382
+ def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
383
+ return super(Hive.Generator, self).renamecolumn_sql(expression)
sqlglot/dialects/tsql.py CHANGED
@@ -670,6 +670,12 @@ class TSQL(Dialect):
670
670
 
671
671
  SET_OP_MODIFIERS = {"offset"}
672
672
 
673
+ ODBC_DATETIME_LITERALS = {
674
+ "d": exp.Date,
675
+ "t": exp.Time,
676
+ "ts": exp.Timestamp,
677
+ }
678
+
673
679
  def _parse_alter_table_set(self) -> exp.AlterSet:
674
680
  return self._parse_wrapped(super()._parse_alter_table_set)
675
681
 
@@ -902,6 +908,11 @@ class TSQL(Dialect):
902
908
 
903
909
  return self.expression(exp.UniqueColumnConstraint, this=this)
904
910
 
911
+ def _parse_update(self) -> exp.Update:
912
+ expression = super()._parse_update()
913
+ expression.set("options", self._parse_options())
914
+ return expression
915
+
905
916
  def _parse_partition(self) -> t.Optional[exp.Partition]:
906
917
  if not self._match_text_seq("WITH", "(", "PARTITIONS"):
907
918
  return None
sqlglot/expressions.py CHANGED
@@ -1838,6 +1838,7 @@ class AlterColumn(Expression):
1838
1838
  "comment": False,
1839
1839
  "allow_null": False,
1840
1840
  "visible": False,
1841
+ "rename_to": False,
1841
1842
  }
1842
1843
 
1843
1844
 
@@ -3633,6 +3634,7 @@ class Update(DML):
3633
3634
  "returning": False,
3634
3635
  "order": False,
3635
3636
  "limit": False,
3637
+ "options": False,
3636
3638
  }
3637
3639
 
3638
3640
  def table(
@@ -4956,6 +4958,7 @@ class Alter(Expression):
4956
4958
  "cluster": False,
4957
4959
  "not_valid": False,
4958
4960
  "check": False,
4961
+ "cascade": False,
4959
4962
  }
4960
4963
 
4961
4964
  @property
@@ -5385,7 +5388,7 @@ class TimeUnit(Expression):
5385
5388
 
5386
5389
  def __init__(self, **args):
5387
5390
  unit = args.get("unit")
5388
- if type(unit) in self.VAR_LIKE:
5391
+ if type(unit) in self.VAR_LIKE and not (isinstance(unit, Column) and len(unit.parts) != 1):
5389
5392
  args["unit"] = Var(
5390
5393
  this=(self.UNABBREVIATED_UNIT_NAME.get(unit.name) or unit.name).upper()
5391
5394
  )
@@ -5525,6 +5528,10 @@ class Coth(Func):
5525
5528
  pass
5526
5529
 
5527
5530
 
5531
+ class Cos(Func):
5532
+ pass
5533
+
5534
+
5528
5535
  class Csc(Func):
5529
5536
  pass
5530
5537
 
@@ -5549,6 +5556,18 @@ class Sinh(Func):
5549
5556
  pass
5550
5557
 
5551
5558
 
5559
+ class Tan(Func):
5560
+ pass
5561
+
5562
+
5563
+ class Degrees(Func):
5564
+ pass
5565
+
5566
+
5567
+ class Cosh(Func):
5568
+ pass
5569
+
5570
+
5552
5571
  class CosineDistance(Func):
5553
5572
  arg_types = {"this": True, "expression": True}
5554
5573
 
@@ -5840,6 +5859,7 @@ class ArrayConstructCompact(Func):
5840
5859
 
5841
5860
 
5842
5861
  class ArrayContains(Binary, Func):
5862
+ arg_types = {"this": True, "expression": True, "ensure_variant": False}
5843
5863
  _sql_names = ["ARRAY_CONTAINS", "ARRAY_HAS"]
5844
5864
 
5845
5865
 
@@ -6172,7 +6192,9 @@ class DateTrunc(Func):
6172
6192
  unabbreviate = args.pop("unabbreviate", True)
6173
6193
 
6174
6194
  unit = args.get("unit")
6175
- if isinstance(unit, TimeUnit.VAR_LIKE):
6195
+ if isinstance(unit, TimeUnit.VAR_LIKE) and not (
6196
+ isinstance(unit, Column) and len(unit.parts) != 1
6197
+ ):
6176
6198
  unit_name = unit.name.upper()
6177
6199
  if unabbreviate and unit_name in TimeUnit.UNABBREVIATED_UNIT_NAME:
6178
6200
  unit_name = TimeUnit.UNABBREVIATED_UNIT_NAME[unit_name]
@@ -6735,7 +6757,13 @@ class JSONExists(Func):
6735
6757
  # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html
6736
6758
  # Note: parsing of JSON column definitions is currently incomplete.
6737
6759
  class JSONColumnDef(Expression):
6738
- arg_types = {"this": False, "kind": False, "path": False, "nested_schema": False}
6760
+ arg_types = {
6761
+ "this": False,
6762
+ "kind": False,
6763
+ "path": False,
6764
+ "nested_schema": False,
6765
+ "ordinality": False,
6766
+ }
6739
6767
 
6740
6768
 
6741
6769
  class JSONSchema(Expression):
@@ -7279,6 +7307,10 @@ class RegexpILike(Binary, Func):
7279
7307
  arg_types = {"this": True, "expression": True, "flag": False}
7280
7308
 
7281
7309
 
7310
+ class RegexpFullMatch(Binary, Func):
7311
+ arg_types = {"this": True, "expression": True, "options": False}
7312
+
7313
+
7282
7314
  class RegexpInstr(Func):
7283
7315
  arg_types = {
7284
7316
  "this": True,
@@ -7380,13 +7412,20 @@ class Soundex(Func):
7380
7412
  pass
7381
7413
 
7382
7414
 
7415
+ # https://docs.snowflake.com/en/sql-reference/functions/soundex_p123
7416
+ class SoundexP123(Func):
7417
+ pass
7418
+
7419
+
7383
7420
  class Split(Func):
7384
7421
  arg_types = {"this": True, "expression": True, "limit": False}
7385
7422
 
7386
7423
 
7387
7424
  # https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.split_part.html
7425
+ # https://docs.snowflake.com/en/sql-reference/functions/split_part
7426
+ # https://docs.snowflake.com/en/sql-reference/functions/strtok
7388
7427
  class SplitPart(Func):
7389
- arg_types = {"this": True, "delimiter": True, "part_index": True}
7428
+ arg_types = {"this": True, "delimiter": False, "part_index": False}
7390
7429
 
7391
7430
 
7392
7431
  # Start may be omitted in the case of postgres
@@ -7430,6 +7469,19 @@ class StrPosition(Func):
7430
7469
  }
7431
7470
 
7432
7471
 
7472
+ # Snowflake: https://docs.snowflake.com/en/sql-reference/functions/search
7473
+ # BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#search
7474
+ class Search(Func):
7475
+ arg_types = {
7476
+ "this": True, # data_to_search / search_data
7477
+ "expression": True, # search_query / search_string
7478
+ "json_scope": False, # BigQuery: JSON_VALUES | JSON_KEYS | JSON_KEYS_AND_VALUES
7479
+ "analyzer": False, # Both: analyzer / ANALYZER
7480
+ "analyzer_options": False, # BigQuery: analyzer_options_values
7481
+ "search_mode": False, # Snowflake: OR | AND
7482
+ }
7483
+
7484
+
7433
7485
  class StrToDate(Func):
7434
7486
  arg_types = {"this": True, "format": False, "safe": False}
7435
7487
 
sqlglot/generator.py CHANGED
@@ -2200,7 +2200,9 @@ class Generator(metaclass=_Generator):
2200
2200
  expression_sql = f"{from_sql}{where_sql}{returning}"
2201
2201
  else:
2202
2202
  expression_sql = f"{returning}{from_sql}{where_sql}"
2203
- sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2203
+ options = self.expressions(expression, key="options")
2204
+ options = f" OPTION({options})" if options else ""
2205
+ sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}"
2204
2206
  return self.prepend_ctes(expression, sql)
2205
2207
 
2206
2208
  def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
@@ -3216,7 +3218,9 @@ class Generator(metaclass=_Generator):
3216
3218
  this = self.sql(expression, "this")
3217
3219
  kind = self.sql(expression, "kind")
3218
3220
  kind = f" {kind}" if kind else ""
3219
- return f"{this}{kind}{path}"
3221
+
3222
+ ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3223
+ return f"{this}{kind}{path}{ordinality}"
3220
3224
 
3221
3225
  def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3222
3226
  return self.func("COLUMNS", *expression.expressions)
@@ -3621,10 +3625,15 @@ class Generator(metaclass=_Generator):
3621
3625
  kind = self.sql(expression, "kind")
3622
3626
  not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3623
3627
  check = " WITH CHECK" if expression.args.get("check") else ""
3628
+ cascade = (
3629
+ " CASCADE"
3630
+ if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3631
+ else ""
3632
+ )
3624
3633
  this = self.sql(expression, "this")
3625
3634
  this = f" {this}" if this else ""
3626
3635
 
3627
- return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}"
3636
+ return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3628
3637
 
3629
3638
  def altersession_sql(self, expression: exp.AlterSession) -> str:
3630
3639
  items_sql = self.expressions(expression, flat=True)
@@ -77,7 +77,7 @@ def coerce_type(node: exp.Expression, promote_to_inferred_datetime_type: bool) -
77
77
  _coerce_date(node.left, node.right, promote_to_inferred_datetime_type)
78
78
  elif isinstance(node, exp.Between):
79
79
  _coerce_date(node.this, node.args["low"], promote_to_inferred_datetime_type)
80
- elif isinstance(node, exp.Extract) and not node.expression.type.is_type(
80
+ elif isinstance(node, exp.Extract) and not node.expression.is_type(
81
81
  *exp.DataType.TEMPORAL_TYPES
82
82
  ):
83
83
  _replace_cast(node.expression, exp.DataType.Type.DATETIME)
@@ -201,6 +201,7 @@ def _mergeable(
201
201
  and not outer_scope.pivots
202
202
  and not any(e.find(exp.AggFunc, exp.Select, exp.Explode) for e in inner_select.expressions)
203
203
  and not (leave_tables_isolated and len(outer_scope.selected_sources) > 1)
204
+ and not (isinstance(from_or_join, exp.Join) and inner_select.args.get("joins"))
204
205
  and not (
205
206
  isinstance(from_or_join, exp.Join)
206
207
  and inner_select.args.get("where")
@@ -282,6 +283,7 @@ def _merge_joins(outer_scope: Scope, inner_scope: Scope, from_or_join: FromOrJoi
282
283
  new_joins = []
283
284
 
284
285
  joins = inner_scope.expression.args.get("joins") or []
286
+
285
287
  for join in joins:
286
288
  new_joins.append(join)
287
289
  outer_scope.add_source(join.alias_or_name, inner_scope.sources[join.alias_or_name])
@@ -19,10 +19,15 @@ def optimize_joins(expression):
19
19
  """
20
20
 
21
21
  for select in expression.find_all(exp.Select):
22
+ joins = select.args.get("joins", [])
23
+
24
+ if not _is_reorderable(joins):
25
+ continue
26
+
22
27
  references = {}
23
28
  cross_joins = []
24
29
 
25
- for join in select.args.get("joins", []):
30
+ for join in joins:
26
31
  tables = other_table_names(join)
27
32
 
28
33
  if tables:
@@ -59,11 +64,20 @@ def reorder_joins(expression):
59
64
  """
60
65
  for from_ in expression.find_all(exp.From):
61
66
  parent = from_.parent
62
- joins = {join.alias_or_name: join for join in parent.args.get("joins", [])}
63
- dag = {name: other_table_names(join) for name, join in joins.items()}
67
+ joins = parent.args.get("joins", [])
68
+
69
+ if not _is_reorderable(joins):
70
+ continue
71
+
72
+ joins_by_name = {join.alias_or_name: join for join in joins}
73
+ dag = {name: other_table_names(join) for name, join in joins_by_name.items()}
64
74
  parent.set(
65
75
  "joins",
66
- [joins[name] for name in tsort(dag) if name != from_.alias_or_name and name in joins],
76
+ [
77
+ joins_by_name[name]
78
+ for name in tsort(dag)
79
+ if name != from_.alias_or_name and name in joins_by_name
80
+ ],
67
81
  )
68
82
  return expression
69
83
 
@@ -90,3 +104,23 @@ def normalize(expression):
90
104
  def other_table_names(join: exp.Join) -> t.Set[str]:
91
105
  on = join.args.get("on")
92
106
  return exp.column_table_names(on, join.alias_or_name) if on else set()
107
+
108
+
109
+ def _is_reorderable(joins: t.List[exp.Join]) -> bool:
110
+ """
111
+ Checks if joins can be reordered without changing query semantics.
112
+
113
+ Joins with a side (LEFT, RIGHT, FULL) cannot be reordered easily,
114
+ the order affects which rows are included in the result.
115
+
116
+ Example:
117
+ >>> from sqlglot import parse_one, exp
118
+ >>> from sqlglot.optimizer.optimize_joins import _is_reorderable
119
+ >>> ast = parse_one("SELECT * FROM x JOIN y ON x.id = y.id JOIN z ON y.id = z.id")
120
+ >>> _is_reorderable(ast.find(exp.Select).args.get("joins", []))
121
+ True
122
+ >>> ast = parse_one("SELECT * FROM x LEFT JOIN y ON x.id = y.id JOIN z ON y.id = z.id")
123
+ >>> _is_reorderable(ast.find(exp.Select).args.get("joins", []))
124
+ False
125
+ """
126
+ return not any(join.side for join in joins)
sqlglot/parser.py CHANGED
@@ -1141,11 +1141,6 @@ class Parser(metaclass=_Parser):
1141
1141
  "TTL": lambda self: self.expression(exp.MergeTreeTTL, expressions=[self._parse_bitwise()]),
1142
1142
  "UNIQUE": lambda self: self._parse_unique(),
1143
1143
  "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint),
1144
- "WATERMARK": lambda self: self.expression(
1145
- exp.WatermarkColumnConstraint,
1146
- this=self._match(TokenType.FOR) and self._parse_column(),
1147
- expression=self._match(TokenType.ALIAS) and self._parse_disjunction(),
1148
- ),
1149
1144
  "WITH": lambda self: self.expression(
1150
1145
  exp.Properties, expressions=self._parse_wrapped_properties()
1151
1146
  ),
@@ -1211,7 +1206,6 @@ class Parser(metaclass=_Parser):
1211
1206
  "PERIOD",
1212
1207
  "PRIMARY KEY",
1213
1208
  "UNIQUE",
1214
- "WATERMARK",
1215
1209
  "BUCKET",
1216
1210
  "TRUNCATE",
1217
1211
  }
@@ -1438,11 +1432,7 @@ class Parser(metaclass=_Parser):
1438
1432
 
1439
1433
  IS_JSON_PREDICATE_KIND = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
1440
1434
 
1441
- ODBC_DATETIME_LITERALS = {
1442
- "d": exp.Date,
1443
- "t": exp.Time,
1444
- "ts": exp.Timestamp,
1445
- }
1435
+ ODBC_DATETIME_LITERALS: t.Dict[str, t.Type[exp.Expression]] = {}
1446
1436
 
1447
1437
  ON_CONDITION_TOKENS = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
1448
1438
 
@@ -1541,6 +1531,9 @@ class Parser(metaclass=_Parser):
1541
1531
  # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
1542
1532
  ALTER_RENAME_REQUIRES_COLUMN = True
1543
1533
 
1534
+ # Whether Alter statements are allowed to contain Partition specifications
1535
+ ALTER_TABLE_PARTITIONS = False
1536
+
1544
1537
  # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
1545
1538
  # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
1546
1539
  # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
@@ -3545,9 +3538,13 @@ class Parser(metaclass=_Parser):
3545
3538
 
3546
3539
  return this
3547
3540
 
3548
- def _parse_query_modifiers(
3549
- self, this: t.Optional[exp.Expression]
3550
- ) -> t.Optional[exp.Expression]:
3541
+ @t.overload
3542
+ def _parse_query_modifiers(self, this: E) -> E: ...
3543
+
3544
+ @t.overload
3545
+ def _parse_query_modifiers(self, this: None) -> None: ...
3546
+
3547
+ def _parse_query_modifiers(self, this):
3551
3548
  if isinstance(this, self.MODIFIABLES):
3552
3549
  for join in self._parse_joins():
3553
3550
  this.append("joins", join)
@@ -4592,21 +4589,11 @@ class Parser(metaclass=_Parser):
4592
4589
  before_with_index = self._index
4593
4590
  with_prefix = self._match(TokenType.WITH)
4594
4591
 
4595
- if self._match(TokenType.ROLLUP):
4596
- elements["rollup"].append(
4597
- self._parse_cube_or_rollup(exp.Rollup, with_prefix=with_prefix)
4598
- )
4599
- elif self._match(TokenType.CUBE):
4600
- elements["cube"].append(
4601
- self._parse_cube_or_rollup(exp.Cube, with_prefix=with_prefix)
4602
- )
4603
- elif self._match(TokenType.GROUPING_SETS):
4604
- elements["grouping_sets"].append(
4605
- self.expression(
4606
- exp.GroupingSets,
4607
- expressions=self._parse_wrapped_csv(self._parse_grouping_set),
4608
- )
4609
- )
4592
+ if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix):
4593
+ key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
4594
+ elements[key].append(cube_or_rollup)
4595
+ elif grouping_sets := self._parse_grouping_sets():
4596
+ elements["grouping_sets"].append(grouping_sets)
4610
4597
  elif self._match_text_seq("TOTALS"):
4611
4598
  elements["totals"] = True # type: ignore
4612
4599
 
@@ -4619,18 +4606,27 @@ class Parser(metaclass=_Parser):
4619
4606
 
4620
4607
  return self.expression(exp.Group, comments=comments, **elements) # type: ignore
4621
4608
 
4622
- def _parse_cube_or_rollup(self, kind: t.Type[E], with_prefix: bool = False) -> E:
4609
+ def _parse_cube_or_rollup(self, with_prefix: bool = False) -> t.Optional[exp.Cube | exp.Rollup]:
4610
+ if self._match(TokenType.CUBE):
4611
+ kind: t.Type[exp.Cube | exp.Rollup] = exp.Cube
4612
+ elif self._match(TokenType.ROLLUP):
4613
+ kind = exp.Rollup
4614
+ else:
4615
+ return None
4616
+
4623
4617
  return self.expression(
4624
4618
  kind, expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_column)
4625
4619
  )
4626
4620
 
4627
- def _parse_grouping_set(self) -> t.Optional[exp.Expression]:
4628
- if self._match(TokenType.L_PAREN):
4629
- grouping_set = self._parse_csv(self._parse_bitwise)
4630
- self._match_r_paren()
4631
- return self.expression(exp.Tuple, expressions=grouping_set)
4621
+ def _parse_grouping_sets(self) -> t.Optional[exp.GroupingSets]:
4622
+ if self._match(TokenType.GROUPING_SETS):
4623
+ return self.expression(
4624
+ exp.GroupingSets, expressions=self._parse_wrapped_csv(self._parse_grouping_set)
4625
+ )
4626
+ return None
4632
4627
 
4633
- return self._parse_column()
4628
+ def _parse_grouping_set(self) -> t.Optional[exp.Expression]:
4629
+ return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
4634
4630
 
4635
4631
  def _parse_having(self, skip_having_token: bool = False) -> t.Optional[exp.Having]:
4636
4632
  if not skip_having_token and not self._match(TokenType.HAVING):
@@ -4749,11 +4745,15 @@ class Parser(metaclass=_Parser):
4749
4745
  exp.Ordered, this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill
4750
4746
  )
4751
4747
 
4752
- def _parse_limit_options(self) -> exp.LimitOptions:
4753
- percent = self._match(TokenType.PERCENT)
4748
+ def _parse_limit_options(self) -> t.Optional[exp.LimitOptions]:
4749
+ percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
4754
4750
  rows = self._match_set((TokenType.ROW, TokenType.ROWS))
4755
4751
  self._match_text_seq("ONLY")
4756
4752
  with_ties = self._match_text_seq("WITH", "TIES")
4753
+
4754
+ if not (percent or rows or with_ties):
4755
+ return None
4756
+
4757
4757
  return self.expression(exp.LimitOptions, percent=percent, rows=rows, with_ties=with_ties)
4758
4758
 
4759
4759
  def _parse_limit(
@@ -4771,10 +4771,13 @@ class Parser(metaclass=_Parser):
4771
4771
  if limit_paren:
4772
4772
  self._match_r_paren()
4773
4773
 
4774
- limit_options = self._parse_limit_options()
4775
4774
  else:
4776
- limit_options = None
4777
- expression = self._parse_term()
4775
+ # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since
4776
+ # we try to build an exp.Mod expr. For that matter, we backtrack and instead
4777
+ # consume the factor plus parse the percentage separately
4778
+ expression = self._try_parse(self._parse_term) or self._parse_factor()
4779
+
4780
+ limit_options = self._parse_limit_options()
4778
4781
 
4779
4782
  if self._match(TokenType.COMMA):
4780
4783
  offset = expression
@@ -5060,8 +5063,12 @@ class Parser(metaclass=_Parser):
5060
5063
  matched_l_paren = self._prev.token_type == TokenType.L_PAREN
5061
5064
  expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
5062
5065
 
5063
- if len(expressions) == 1 and isinstance(expressions[0], exp.Query):
5064
- this = self.expression(exp.In, this=this, query=expressions[0].subquery(copy=False))
5066
+ if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
5067
+ this = self.expression(
5068
+ exp.In,
5069
+ this=this,
5070
+ query=self._parse_query_modifiers(query).subquery(copy=False),
5071
+ )
5065
5072
  else:
5066
5073
  this = self.expression(exp.In, this=this, expressions=expressions)
5067
5074
 
@@ -5791,14 +5798,17 @@ class Parser(metaclass=_Parser):
5791
5798
  else:
5792
5799
  expressions = self._parse_expressions()
5793
5800
 
5794
- this = self._parse_query_modifiers(seq_get(expressions, 0))
5801
+ this = seq_get(expressions, 0)
5795
5802
 
5796
5803
  if not this and self._match(TokenType.R_PAREN, advance=False):
5797
5804
  this = self.expression(exp.Tuple)
5798
5805
  elif isinstance(this, exp.UNWRAPPED_QUERIES):
5799
5806
  this = self._parse_subquery(this=this, parse_alias=False)
5800
5807
  elif isinstance(this, exp.Subquery):
5801
- this = self._parse_subquery(this=self._parse_set_operations(this), parse_alias=False)
5808
+ this = self._parse_subquery(
5809
+ this=self._parse_query_modifiers(self._parse_set_operations(this)),
5810
+ parse_alias=False,
5811
+ )
5802
5812
  elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
5803
5813
  this = self.expression(exp.Tuple, expressions=expressions)
5804
5814
  else:
@@ -6911,10 +6921,12 @@ class Parser(metaclass=_Parser):
6911
6921
  def _parse_json_column_def(self) -> exp.JSONColumnDef:
6912
6922
  if not self._match_text_seq("NESTED"):
6913
6923
  this = self._parse_id_var()
6924
+ ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
6914
6925
  kind = self._parse_types(allow_identifiers=False)
6915
6926
  nested = None
6916
6927
  else:
6917
6928
  this = None
6929
+ ordinality = None
6918
6930
  kind = None
6919
6931
  nested = True
6920
6932
 
@@ -6927,6 +6939,7 @@ class Parser(metaclass=_Parser):
6927
6939
  kind=kind,
6928
6940
  path=path,
6929
6941
  nested_schema=nested_schema,
6942
+ ordinality=ordinality,
6930
6943
  )
6931
6944
 
6932
6945
  def _parse_json_schema(self) -> exp.JSONSchema:
@@ -7722,7 +7735,7 @@ class Parser(metaclass=_Parser):
7722
7735
  check = None
7723
7736
  cluster = None
7724
7737
  else:
7725
- this = self._parse_table(schema=True)
7738
+ this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
7726
7739
  check = self._match_text_seq("WITH", "CHECK")
7727
7740
  cluster = self._parse_on_property() if self._match(TokenType.ON) else None
7728
7741
 
@@ -7734,6 +7747,7 @@ class Parser(metaclass=_Parser):
7734
7747
  actions = ensure_list(parser(self))
7735
7748
  not_valid = self._match_text_seq("NOT", "VALID")
7736
7749
  options = self._parse_csv(self._parse_property)
7750
+ cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
7737
7751
 
7738
7752
  if not self._curr and actions:
7739
7753
  return self.expression(
@@ -7747,6 +7761,7 @@ class Parser(metaclass=_Parser):
7747
7761
  cluster=cluster,
7748
7762
  not_valid=not_valid,
7749
7763
  check=check,
7764
+ cascade=cascade,
7750
7765
  )
7751
7766
 
7752
7767
  return self._parse_as_command(start)
@@ -8642,10 +8657,10 @@ class Parser(metaclass=_Parser):
8642
8657
  args: t.List[exp.Expression] = []
8643
8658
 
8644
8659
  if self._match(TokenType.DISTINCT):
8645
- args.append(self.expression(exp.Distinct, expressions=[self._parse_assignment()]))
8660
+ args.append(self.expression(exp.Distinct, expressions=[self._parse_lambda()]))
8646
8661
  self._match(TokenType.COMMA)
8647
8662
 
8648
- args.extend(self._parse_csv(self._parse_assignment))
8663
+ args.extend(self._parse_function_args())
8649
8664
 
8650
8665
  return self.expression(
8651
8666
  expr_type, this=seq_get(args, 0), expression=seq_get(args, 1), count=seq_get(args, 2)
sqlglot/tokens.py CHANGED
@@ -1421,7 +1421,11 @@ class Tokenizer(metaclass=_Tokenizer):
1421
1421
  raise_unmatched=not self.HEREDOC_TAG_IS_IDENTIFIER,
1422
1422
  )
1423
1423
 
1424
- if tag and self.HEREDOC_TAG_IS_IDENTIFIER and (self._end or not tag.isidentifier()):
1424
+ if (
1425
+ tag
1426
+ and self.HEREDOC_TAG_IS_IDENTIFIER
1427
+ and (self._end or tag.isdigit() or any(c.isspace() for c in tag))
1428
+ ):
1425
1429
  if not self._end:
1426
1430
  self._advance(-1)
1427
1431
 
sqlglot/transforms.py CHANGED
@@ -131,39 +131,6 @@ def unnest_generate_series(expression: exp.Expression) -> exp.Expression:
131
131
  return expression
132
132
 
133
133
 
134
- def unalias_group(expression: exp.Expression) -> exp.Expression:
135
- """
136
- Replace references to select aliases in GROUP BY clauses.
137
-
138
- Example:
139
- >>> import sqlglot
140
- >>> sqlglot.parse_one("SELECT a AS b FROM x GROUP BY b").transform(unalias_group).sql()
141
- 'SELECT a AS b FROM x GROUP BY 1'
142
-
143
- Args:
144
- expression: the expression that will be transformed.
145
-
146
- Returns:
147
- The transformed expression.
148
- """
149
- if isinstance(expression, exp.Group) and isinstance(expression.parent, exp.Select):
150
- aliased_selects = {
151
- e.alias: i
152
- for i, e in enumerate(expression.parent.expressions, start=1)
153
- if isinstance(e, exp.Alias)
154
- }
155
-
156
- for group_by in expression.expressions:
157
- if (
158
- isinstance(group_by, exp.Column)
159
- and not group_by.table
160
- and group_by.name in aliased_selects
161
- ):
162
- group_by.replace(exp.Literal.number(aliased_selects.get(group_by.name)))
163
-
164
- return expression
165
-
166
-
167
134
  def eliminate_distinct_on(expression: exp.Expression) -> exp.Expression:
168
135
  """
169
136
  Convert SELECT DISTINCT ON statements to a subquery with a window function.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglot
3
- Version: 27.20.0
3
+ Version: 27.22.0
4
4
  Summary: An easily customizable SQL parser and transpiler
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License-Expression: MIT
@@ -33,7 +33,7 @@ Requires-Dist: typing_extensions; extra == "dev"
33
33
  Requires-Dist: maturin<2.0,>=1.4; extra == "dev"
34
34
  Requires-Dist: pyperf; extra == "dev"
35
35
  Provides-Extra: rs
36
- Requires-Dist: sqlglotrs==0.6.2; extra == "rs"
36
+ Requires-Dist: sqlglotrs==0.7.0; extra == "rs"
37
37
  Dynamic: license-file
38
38
  Dynamic: provides-extra
39
39
 
@@ -1,57 +1,57 @@
1
1
  sqlglot/__init__.py,sha256=za08rtdPh2v7dOpGdNomttlIVGgTrKja7rPd6sQwaTg,5391
2
2
  sqlglot/__main__.py,sha256=022c173KqxsiABWTEpUIq_tJUxuNiW7a7ABsxBXqvu8,2069
3
3
  sqlglot/_typing.py,sha256=-1HPyr3w5COlSJWqlgt8jhFk2dyMvBuvVBqIX1wyVCM,642
4
- sqlglot/_version.py,sha256=W-gB5oTd5rWsYZXOhmfTMtDc_wY3Dl-VUjAJOtLX5qo,708
4
+ sqlglot/_version.py,sha256=E5VE3-EaKBoByAYk3KI0bC-SdIUPpA7ebugcTIcI_gA,708
5
5
  sqlglot/diff.py,sha256=PtOllQMQa1Sw1-V2Y8eypmDqGujXYPaTOp_WLsWkAWk,17314
6
6
  sqlglot/errors.py,sha256=QNKMr-pzLUDR-tuMmn_GK6iMHUIVdb_YSJ_BhGEvuso,2126
7
- sqlglot/expressions.py,sha256=jQs1lR1Nh5nmXYEjaYVakXgMijNgaWmh1fsOOZ10imE,260181
8
- sqlglot/generator.py,sha256=KyFuqWQpawTj3rWV7ONKO4euqVTzV8aFU3desDu8fso,226565
7
+ sqlglot/expressions.py,sha256=PTwHBbIp3jRz7sjVKZShHJuzlkSg_8rNTxsONy0gfAc,261611
8
+ sqlglot/generator.py,sha256=CmYKDYSuwgPjgRsDlf3e__PFeTdojkUCCplMu4xT4qc,226966
9
9
  sqlglot/helper.py,sha256=OOt5_Mbmnl4Uy6WO6v7DR1iLPcb3v6ITybpq6usf3jw,14471
10
10
  sqlglot/jsonpath.py,sha256=SQgaxzaEYBN7At9dkTK4N1Spk6xHxvHL6QtCIP6iM30,7905
11
11
  sqlglot/lineage.py,sha256=Qj5ykuDNcATppb9vOjoIKBqRVLbu3OMPiZk9f3iyv40,15312
12
- sqlglot/parser.py,sha256=bVpU72Ace-vGXkiqoojKXFGRpXxProri31Q3WP1TQuE,337453
12
+ sqlglot/parser.py,sha256=N7pgpA20wuXDU2Wo1zjjEGuBcl3fZyBMp_Ss3nLppoY,338172
13
13
  sqlglot/planner.py,sha256=ql7Li-bWJRcyXzNaZy_n6bQ6B2ZfunEIB8Ztv2xaxq4,14634
14
14
  sqlglot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  sqlglot/schema.py,sha256=13H2qKQs27EKdTpDLOvcNnSTDAUbYNKjWtJs4aQCSOA,20509
16
16
  sqlglot/serde.py,sha256=nWpBFUjwZh06Li4qBuNb0YRU_QyflzSVyWkFxujM0WM,3175
17
17
  sqlglot/time.py,sha256=Q62gv6kL40OiRBF6BMESxKJcMVn7ZLNw7sv8H34z5FI,18400
18
- sqlglot/tokens.py,sha256=M6E-2vbIs41CYwyLIFtRqre9Mh9kO7Qt9rQvpfVeB7w,49217
19
- sqlglot/transforms.py,sha256=utNDsCBsA7hPUK3-aby3DDgiY_XVMAKQqeoLm1EyihI,41218
18
+ sqlglot/tokens.py,sha256=46CMEmRhJOa3eNagIIK9ndm-_ZSwnaV-AqgZs-SHNro,49321
19
+ sqlglot/transforms.py,sha256=Nx0AM6b2ApfPOcbTlz9_m6DiauWMKC4s4Xe0yuuBmYk,40175
20
20
  sqlglot/trie.py,sha256=v27uXMrHfqrXlJ6GmeTSMovsB_3o0ctnlKhdNt7W6fI,2245
21
21
  sqlglot/dialects/__init__.py,sha256=g3HRtyb32r3LooiHKTzuUNB0_rBO_RauuOegp42gB48,3811
22
22
  sqlglot/dialects/athena.py,sha256=ofArmayYLev4qZQ15GM8mevG04qqR5WGFb2ZcuYm6x4,10966
23
- sqlglot/dialects/bigquery.py,sha256=m3Tk_rieNds4i-t0v-dELB5MvLfIvJY3ozuU6FItXJ8,72758
23
+ sqlglot/dialects/bigquery.py,sha256=l_t70png3A83zEDWUBBsCbiM40HfKIP03Wuq3Zt9CUI,72889
24
24
  sqlglot/dialects/clickhouse.py,sha256=6kx1cm0YhtHbg5kvcY64Hau2KdeC7Y26SVlVHGLyPEA,58579
25
25
  sqlglot/dialects/databricks.py,sha256=H4QTq7gg6tJylKc_YWsGp6049KydoI_wlQUHM7iCJtI,4753
26
- sqlglot/dialects/dialect.py,sha256=KfBctpr7VdrCdHrP1Tk7CqAml53tRq9x-aDAkaN-9l0,73540
26
+ sqlglot/dialects/dialect.py,sha256=wUobB-jvOgd_JucRCyUYK6qRx2Hts8mQKmk8kgwWkdc,73979
27
27
  sqlglot/dialects/doris.py,sha256=CFnF955Oav3IjZWA80ickOI8tPpCjxk7BN5R4Z6pA1U,25263
28
28
  sqlglot/dialects/dremio.py,sha256=nOMxu_4xVKSOmMGNSwdxXSPc243cNbbpb-xXzYdgdeg,8460
29
29
  sqlglot/dialects/drill.py,sha256=FOh7_KjPx_77pv0DiHKZog0CcmzqeF9_PEmGnJ1ESSM,5825
30
30
  sqlglot/dialects/druid.py,sha256=kh3snZtneehNOWqs3XcPjsrhNaRbkCQ8E4hHbWJ1fHM,690
31
- sqlglot/dialects/duckdb.py,sha256=hLbLqkh5X5Nx3y5yfbBc5h9ye6UWTiZr_VsS4BMY5Rw,54612
31
+ sqlglot/dialects/duckdb.py,sha256=xennAC2Gh3eImkpHo0-cf4BBzcAKx-HkexyZfQMiUCo,54655
32
32
  sqlglot/dialects/dune.py,sha256=gALut-fFfN2qMsr8LvZ1NQK3F3W9z2f4PwMvTMXVVVg,375
33
33
  sqlglot/dialects/exasol.py,sha256=ay3g_VyT5WvHTgNyJuCQu0nBt4bpllLZ9IdMBizEgYM,15761
34
34
  sqlglot/dialects/fabric.py,sha256=BdkvzM8s-m5DIdBwdjEYskp32ub7aHCAex_xlhQn92I,10222
35
- sqlglot/dialects/hive.py,sha256=UGIkXjMCk5a9ndUXQtvfG560oi3emdpqOYLQCmGabBk,32046
35
+ sqlglot/dialects/hive.py,sha256=Uw-7Y1LnYOdcv71jCIZXhMvJAWwU5AVcFlIuM-YArnY,34530
36
36
  sqlglot/dialects/materialize.py,sha256=LD2q1kTRrCwkIu1BfoBvnjTGbupDtoQ8JQMDCIYAXHg,3533
37
37
  sqlglot/dialects/mysql.py,sha256=xxVAR-pXMljYCUioavP3nROtOqKmK4kfdp4WWXX7X9g,50049
38
- sqlglot/dialects/oracle.py,sha256=zWPCpzGiTlgCJ5E6FjfX3Rszjcw4SnHg6xeVboMYIyo,15972
38
+ sqlglot/dialects/oracle.py,sha256=qB6Ga0Si2-TpVNqU_2COvWESIUYNL32rYk_BC9aiujE,15898
39
39
  sqlglot/dialects/postgres.py,sha256=_pXSu29684utgeuzPziSJ0Sw54WEIIunwLugJw7KFD8,34853
40
- sqlglot/dialects/presto.py,sha256=XVeYr2NP86x5enlRqI7MYR6le85_ucYg_BBRocGN3jM,33413
40
+ sqlglot/dialects/presto.py,sha256=5C6I_aDC-9CDrLfY97EFsUWymaS3B7aW_-h-mHleWIQ,33339
41
41
  sqlglot/dialects/prql.py,sha256=fwN-SPEGx-drwf1K0U2MByN-PkW3C_rOgQ3xeJeychg,7908
42
42
  sqlglot/dialects/redshift.py,sha256=FIwtP3yEg-way9pa32kxCJc6IaFkHVIvgYKZA-Ilmi0,15919
43
- sqlglot/dialects/risingwave.py,sha256=BqWwW1iT_OIVMwfRamaww79snnBwIgCfr22Go-ggO68,3289
43
+ sqlglot/dialects/risingwave.py,sha256=Wd-I_Hbwl-6Rgf_NM0I_axliInY418k2kaAWRCmaqyE,3791
44
44
  sqlglot/dialects/singlestore.py,sha256=0QqNYOucNklPQuyeGcsisLI97qPGx_RfWKOFarJz2qw,61711
45
- sqlglot/dialects/snowflake.py,sha256=_SXsdDPNopi78MT7R_YHZHoY6WDB_tZ6pVvk5VcJaYQ,79485
45
+ sqlglot/dialects/snowflake.py,sha256=JZfQ9QO_67TIpI36HIkk77gsjJWsZ1eOZWmWZpoUkjc,82018
46
46
  sqlglot/dialects/solr.py,sha256=pydnl4ml-3M1Fc4ALm6cMVO9h-5EtqZxPZH_91Nz1Ss,617
47
- sqlglot/dialects/spark.py,sha256=PzyhkelDzbCMgJ3RVHD6yyzLIFp9NdZfwVas5IymowM,10147
48
- sqlglot/dialects/spark2.py,sha256=qz36FT9k4iuiqboRpyG4VpKGkPR0P2fifmqgZ9gNUEU,14851
47
+ sqlglot/dialects/spark.py,sha256=mt3Twh0_EJelYy_7HLinDEQ1Chj2EYMjeLCPLRzAJXY,10113
48
+ sqlglot/dialects/spark2.py,sha256=s4RTOGunYT1_HJt4KbhBWK_eOgmtzlpBCQCl60KEPAQ,15621
49
49
  sqlglot/dialects/sqlite.py,sha256=FuEDDyKZeeWVblknhFSMX7dNoS-ci5ktXpSXZeBK5xA,13592
50
50
  sqlglot/dialects/starrocks.py,sha256=-NWQa2gJbiMMfLauX-Jy9ciJ5DUzUOk2QkPbhglz5W4,11446
51
51
  sqlglot/dialects/tableau.py,sha256=oIawDzUITxGCWaEMB8OaNMPWhbC3U-2y09pYPm4eazc,2190
52
52
  sqlglot/dialects/teradata.py,sha256=7LxCcRwP0Idd_OnCzA57NCdheVjHcKC2aFAKG5N49IU,18202
53
53
  sqlglot/dialects/trino.py,sha256=Z7prRhCxIBh0KCxIQpWmVOIGHCJM9Xl5oRlqySxln4Y,4350
54
- sqlglot/dialects/tsql.py,sha256=7pVL3H-qNLCnoHqBEVSIVKhlTqoPmiYBRtg_HVv8zH4,54462
54
+ sqlglot/dialects/tsql.py,sha256=w4wdIwYiA9JY4JvESYLEqp-KDvRR89MjE88rdTPK410,54783
55
55
  sqlglot/executor/__init__.py,sha256=FslewzYQtQdDNg_0Ju2UaiP4vo4IMUgkfkmFsYUhcN0,2958
56
56
  sqlglot/executor/context.py,sha256=WJHJdYQCOeVXwLw0uSSrWSc25eBMn5Ix108RCvdsKRQ,3386
57
57
  sqlglot/executor/env.py,sha256=tQhU5PpTBMcxgZIFddFqxWMNPtHN0vOOz72voncY3KY,8276
@@ -59,15 +59,15 @@ sqlglot/executor/python.py,sha256=09GYRzrPn3lZGfDJY9pbONOvmYxsRyeSWjUiqkSRHGo,16
59
59
  sqlglot/executor/table.py,sha256=xkuJlgLVNYUXsSUaX0zTcnFekldXLLU8LqDyjR5K9wY,4419
60
60
  sqlglot/optimizer/__init__.py,sha256=FdAvVz6rQLLkiiH21-SD4RxB5zS3WDeU-s03PZkJ-F4,343
61
61
  sqlglot/optimizer/annotate_types.py,sha256=kCPwrbBXSiO0oJUlkw8NkdsoLgjJ4l5LOouCHPqOFlA,26728
62
- sqlglot/optimizer/canonicalize.py,sha256=RJpUbWDudjknRMtO_Kf8MGZ5Hv1twpPWac2u5kpV4Vw,7719
62
+ sqlglot/optimizer/canonicalize.py,sha256=5Yc6cFAd1gENqJ3OqejsUE40MV4vqQ-PqBnJXV3SMj8,7714
63
63
  sqlglot/optimizer/eliminate_ctes.py,sha256=fUBM0RUnPrm2sYptEWBux98B7fcx7W-BM1zVqfgDz9c,1448
64
64
  sqlglot/optimizer/eliminate_joins.py,sha256=2iYtG93aJGxvURqm1BVPosrnnnQ_IXI14RcD4pM8eHc,5942
65
65
  sqlglot/optimizer/eliminate_subqueries.py,sha256=sAB_Pk94_n2n1PIaZ2Mc3M-n2TV-JmjjaomaY14u0Og,6292
66
66
  sqlglot/optimizer/isolate_table_selects.py,sha256=_8rIKVMoL7eY3rrJsmgIdTRvfmBSLUxeHg42q1JW990,1464
67
- sqlglot/optimizer/merge_subqueries.py,sha256=-4C80Hob7gqJDHkt3IeH0oExmqPuU9RGB7JC_hlqr7s,15443
67
+ sqlglot/optimizer/merge_subqueries.py,sha256=tis4la3HeAsglhYcLu9EMaVGsNiyecq5iwHkfmW0WQU,15532
68
68
  sqlglot/optimizer/normalize.py,sha256=wu3GeKY36PLyAb9f534jDDfzDwvZJpZ8g_H5QH6acZQ,6667
69
69
  sqlglot/optimizer/normalize_identifiers.py,sha256=uD4xICJAgj0X7EFc2LYcDWxAW2aTHANO2wy7kfn9gfY,2098
70
- sqlglot/optimizer/optimize_joins.py,sha256=tfEnTqBofveBXNKJ30GIvm2lyagAuD24bMNfu3iQi_k,3043
70
+ sqlglot/optimizer/optimize_joins.py,sha256=nnfRpL03lpDQF1oBO1EgaABqnr6t2GP6uMWoSLPW3IQ,4120
71
71
  sqlglot/optimizer/optimizer.py,sha256=vXEXDWHvbO-vJmSI7UqJuydM2WrD1xko7rETq2EtVJo,3533
72
72
  sqlglot/optimizer/pushdown_predicates.py,sha256=HGjs3Z4V3-X2d1VTfWhyByY3aL5SmKnVvt3aDXiiBM0,8414
73
73
  sqlglot/optimizer/pushdown_projections.py,sha256=7NoK5NAUVYVhs0YnYyo6WuXfaO-BShSwS6lA8Y-ATQ4,6668
@@ -77,8 +77,8 @@ sqlglot/optimizer/qualify_tables.py,sha256=dA4ZazL7ShQh2JgBwpHuG-4c5lBw1TNzCnuN7
77
77
  sqlglot/optimizer/scope.py,sha256=UOTrbwqcTc5iRQf0WStgYWXpE24w6riZy-tJYA18yTw,31229
78
78
  sqlglot/optimizer/simplify.py,sha256=27IYsqbz1kyMlURSfRkm_ADSQJg-4805AOMFOjKKytU,51049
79
79
  sqlglot/optimizer/unnest_subqueries.py,sha256=kzWUVDlxs8z9nmRx-8U-pHXPtVZhEIwkKqmKhr2QLvc,10908
80
- sqlglot-27.20.0.dist-info/licenses/LICENSE,sha256=p1Yk0B4oa0l8Rh-_dYyy75d8spjPd_vTloXfz4FWxys,1065
81
- sqlglot-27.20.0.dist-info/METADATA,sha256=nO_Q85ybayInmXwdQV_wFMHF4kQSA0ujLlWo-H5r0I8,20825
82
- sqlglot-27.20.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
- sqlglot-27.20.0.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
84
- sqlglot-27.20.0.dist-info/RECORD,,
80
+ sqlglot-27.22.0.dist-info/licenses/LICENSE,sha256=p1Yk0B4oa0l8Rh-_dYyy75d8spjPd_vTloXfz4FWxys,1065
81
+ sqlglot-27.22.0.dist-info/METADATA,sha256=aoHNF0rhdj1BNAUJDXumHET2la45gugovXSPzFCXL4k,20825
82
+ sqlglot-27.22.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
+ sqlglot-27.22.0.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
84
+ sqlglot-27.22.0.dist-info/RECORD,,