sqlglot 26.26.0__py3-none-any.whl → 26.28.1__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
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '26.26.0'
21
- __version_tuple__ = version_tuple = (26, 26, 0)
20
+ __version__ = version = '26.28.1'
21
+ __version_tuple__ = version_tuple = (26, 28, 1)
sqlglot/dialects/hive.py CHANGED
@@ -557,6 +557,7 @@ class Hive(Dialect):
557
557
  exp.GenerateDateArray: sequence_sql,
558
558
  exp.If: if_sql(),
559
559
  exp.ILike: no_ilike_sql,
560
+ exp.IntDiv: lambda self, e: self.binary(e, "DIV"),
560
561
  exp.IsNan: rename_func("ISNAN"),
561
562
  exp.JSONExtract: lambda self, e: self.func("GET_JSON_OBJECT", e.this, e.expression),
562
563
  exp.JSONExtractScalar: lambda self, e: self.func(
sqlglot/dialects/mysql.py CHANGED
@@ -489,6 +489,27 @@ class MySQL(Dialect):
489
489
  VALUES_FOLLOWED_BY_PAREN = False
490
490
  SUPPORTS_PARTITION_SELECTION = True
491
491
 
492
+ def _parse_generated_as_identity(
493
+ self,
494
+ ) -> (
495
+ exp.GeneratedAsIdentityColumnConstraint
496
+ | exp.ComputedColumnConstraint
497
+ | exp.GeneratedAsRowColumnConstraint
498
+ ):
499
+ this = super()._parse_generated_as_identity()
500
+
501
+ if self._match_texts(("STORED", "VIRTUAL")):
502
+ persisted = self._prev.text.upper() == "STORED"
503
+
504
+ if isinstance(this, exp.ComputedColumnConstraint):
505
+ this.set("persisted", persisted)
506
+ elif isinstance(this, exp.GeneratedAsIdentityColumnConstraint):
507
+ this = self.expression(
508
+ exp.ComputedColumnConstraint, this=this.expression, persisted=persisted
509
+ )
510
+
511
+ return this
512
+
492
513
  def _parse_primary_key_part(self) -> t.Optional[exp.Expression]:
493
514
  this = self._parse_id_var()
494
515
  if not self._match(TokenType.L_PAREN):
@@ -1154,6 +1175,10 @@ class MySQL(Dialect):
1154
1175
  "zerofill",
1155
1176
  }
1156
1177
 
1178
+ def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1179
+ persisted = "STORED" if expression.args.get("persisted") else "VIRTUAL"
1180
+ return f"GENERATED ALWAYS AS ({self.sql(expression.this.unnest())}) {persisted}"
1181
+
1157
1182
  def array_sql(self, expression: exp.Array) -> str:
1158
1183
  self.unsupported("Arrays are not supported by MySQL")
1159
1184
  return self.function_fallback_sql(expression)
@@ -512,6 +512,18 @@ class Postgres(Dialect):
512
512
 
513
513
  return this
514
514
 
515
+ def _parse_user_defined_type(
516
+ self, identifier: exp.Identifier
517
+ ) -> t.Optional[exp.Expression]:
518
+ udt_type: exp.Identifier | exp.Dot = identifier
519
+
520
+ while self._match(TokenType.DOT):
521
+ part = self._parse_id_var()
522
+ if part:
523
+ udt_type = exp.Dot(this=udt_type, expression=part)
524
+
525
+ return exp.DataType.build(udt_type, udt=True)
526
+
515
527
  class Generator(generator.Generator):
516
528
  SINGLE_STRING_INTERVAL = True
517
529
  RENAME_TABLE_WITH_DB = False
@@ -863,8 +863,14 @@ class Snowflake(Dialect):
863
863
  properties=self._parse_properties(),
864
864
  )
865
865
 
866
- def _parse_get(self) -> exp.Get | exp.Command:
866
+ def _parse_get(self) -> t.Optional[exp.Expression]:
867
867
  start = self._prev
868
+
869
+ # If we detect GET( then we need to parse a function, not a statement
870
+ if self._match(TokenType.L_PAREN):
871
+ self._retreat(self._index - 2)
872
+ return self._parse_expression()
873
+
868
874
  target = self._parse_location_path()
869
875
 
870
876
  # Parse as command if unquoted file path
@@ -1100,6 +1106,7 @@ class Snowflake(Dialect):
1100
1106
  self, e, func_name="CHARINDEX", supports_position=True
1101
1107
  ),
1102
1108
  exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)),
1109
+ exp.StringToArray: rename_func("STRTOK_TO_ARRAY"),
1103
1110
  exp.Stuff: rename_func("INSERT"),
1104
1111
  exp.StPoint: rename_func("ST_MAKEPOINT"),
1105
1112
  exp.TimeAdd: date_delta_sql("TIMEADD"),
@@ -109,7 +109,9 @@ class SQLite(Dialect):
109
109
  "DATETIME": lambda args: exp.Anonymous(this="DATETIME", expressions=args),
110
110
  "TIME": lambda args: exp.Anonymous(this="TIME", expressions=args),
111
111
  }
112
+
112
113
  STRING_ALIASES = True
114
+ ALTER_RENAME_REQUIRES_COLUMN = False
113
115
 
114
116
  def _parse_unique(self) -> exp.UniqueColumnConstraint:
115
117
  # Do not consume more tokens if UNIQUE is used as a standalone constraint, e.g:
sqlglot/expressions.py CHANGED
@@ -51,8 +51,8 @@ class _Expression(type):
51
51
  def __new__(cls, clsname, bases, attrs):
52
52
  klass = super().__new__(cls, clsname, bases, attrs)
53
53
 
54
- # When an Expression class is created, its key is automatically set to be
55
- # the lowercase version of the class' name.
54
+ # When an Expression class is created, its key is automatically set
55
+ # to be the lowercase version of the class' name.
56
56
  klass.key = clsname.lower()
57
57
 
58
58
  # This is so that docstrings are not inherited in pdoc
@@ -1724,15 +1724,15 @@ class Column(Condition):
1724
1724
  if self.args.get(part)
1725
1725
  ]
1726
1726
 
1727
- def to_dot(self) -> Dot | Identifier:
1727
+ def to_dot(self, include_dots: bool = True) -> Dot | Identifier:
1728
1728
  """Converts the column into a dot expression."""
1729
1729
  parts = self.parts
1730
1730
  parent = self.parent
1731
1731
 
1732
- while parent:
1733
- if isinstance(parent, Dot):
1732
+ if include_dots:
1733
+ while isinstance(parent, Dot):
1734
1734
  parts.append(parent.expression)
1735
- parent = parent.parent
1735
+ parent = parent.parent
1736
1736
 
1737
1737
  return Dot.build(deepcopy(parts)) if len(parts) > 1 else parts[0]
1738
1738
 
@@ -4753,6 +4753,8 @@ class DataType(Expression):
4753
4753
  if udt:
4754
4754
  return DataType(this=DataType.Type.USERDEFINED, kind=dtype, **kwargs)
4755
4755
  raise
4756
+ elif isinstance(dtype, (Identifier, Dot)) and udt:
4757
+ return DataType(this=DataType.Type.USERDEFINED, kind=dtype, **kwargs)
4756
4758
  elif isinstance(dtype, DataType.Type):
4757
4759
  data_type_exp = DataType(this=dtype)
4758
4760
  elif isinstance(dtype, DataType):
@@ -4794,9 +4796,6 @@ class DataType(Expression):
4794
4796
  return False
4795
4797
 
4796
4798
 
4797
- DATA_TYPE = t.Union[str, DataType, DataType.Type]
4798
-
4799
-
4800
4799
  # https://www.postgresql.org/docs/15/datatype-pseudo.html
4801
4800
  class PseudoType(DataType):
4802
4801
  arg_types = {"this": True}
@@ -4926,6 +4925,10 @@ class AddConstraint(Expression):
4926
4925
  arg_types = {"expressions": True}
4927
4926
 
4928
4927
 
4928
+ class AddPartition(Expression):
4929
+ arg_types = {"this": True, "exists": False}
4930
+
4931
+
4929
4932
  class AttachOption(Expression):
4930
4933
  arg_types = {"this": True, "expression": False}
4931
4934
 
@@ -5026,6 +5029,9 @@ class Dot(Binary):
5026
5029
  return parts
5027
5030
 
5028
5031
 
5032
+ DATA_TYPE = t.Union[str, Identifier, Dot, DataType, DataType.Type]
5033
+
5034
+
5029
5035
  class DPipe(Binary):
5030
5036
  arg_types = {"this": True, "expression": True, "safe": False}
5031
5037
 
@@ -5587,7 +5593,7 @@ class String(Func):
5587
5593
 
5588
5594
 
5589
5595
  class StringToArray(Func):
5590
- arg_types = {"this": True, "expression": True, "null": False}
5596
+ arg_types = {"this": True, "expression": False, "null": False}
5591
5597
  _sql_names = ["STRING_TO_ARRAY", "SPLIT_BY_STRING", "STRTOK_TO_ARRAY"]
5592
5598
 
5593
5599
 
sqlglot/generator.py CHANGED
@@ -3498,6 +3498,10 @@ class Generator(metaclass=_Generator):
3498
3498
  def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3499
3499
  return f"ADD {self.expressions(expression)}"
3500
3500
 
3501
+ def addpartition_sql(self, expression: exp.AddPartition) -> str:
3502
+ exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3503
+ return f"ADD {exists}{self.sql(expression.this)}"
3504
+
3501
3505
  def distinct_sql(self, expression: exp.Distinct) -> str:
3502
3506
  this = self.expressions(expression, flat=True)
3503
3507
 
@@ -758,6 +758,8 @@ def _traverse_tables(scope):
758
758
  expressions.extend(join.this for join in expression.args.get("joins") or [])
759
759
  continue
760
760
 
761
+ child_scope = None
762
+
761
763
  for child_scope in _traverse_scope(
762
764
  scope.branch(
763
765
  expression,
@@ -775,8 +777,9 @@ def _traverse_tables(scope):
775
777
  sources[expression.alias] = child_scope
776
778
 
777
779
  # append the final child_scope yielded
778
- scopes.append(child_scope)
779
- scope.table_scopes.append(child_scope)
780
+ if child_scope:
781
+ scopes.append(child_scope)
782
+ scope.table_scopes.append(child_scope)
780
783
 
781
784
  scope.sources.update(sources)
782
785
 
sqlglot/parser.py CHANGED
@@ -932,11 +932,14 @@ class Parser(metaclass=_Parser):
932
932
 
933
933
  PIPE_SYNTAX_TRANSFORM_PARSERS = {
934
934
  "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
935
- "WHERE": lambda self, query: self._parse_pipe_syntax_where(query),
936
- "ORDER BY": lambda self, query: query.order_by(self._parse_order(), copy=False),
935
+ "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
936
+ "ORDER BY": lambda self, query: query.order_by(
937
+ self._parse_order(), append=False, copy=False
938
+ ),
937
939
  "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
938
- "OFFSET": lambda self, query: query.offset(self._parse_offset(), copy=False),
939
940
  "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
941
+ "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
942
+ "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
940
943
  }
941
944
 
942
945
  PROPERTY_PARSERS: t.Dict[str, t.Callable] = {
@@ -1125,97 +1128,6 @@ class Parser(metaclass=_Parser):
1125
1128
  "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
1126
1129
  }
1127
1130
 
1128
- def _parse_pipe_syntax_select(self, query: exp.Query) -> exp.Query:
1129
- select = self._parse_select()
1130
- if isinstance(select, exp.Select):
1131
- return select.from_(query.subquery(copy=False), copy=False)
1132
- return query
1133
-
1134
- def _parse_pipe_syntax_where(self, query: exp.Query) -> exp.Query:
1135
- where = self._parse_where()
1136
- return query.where(where, copy=False)
1137
-
1138
- def _parse_pipe_syntax_limit(self, query: exp.Query) -> exp.Query:
1139
- limit = self._parse_limit()
1140
- offset = self._parse_offset()
1141
- if limit:
1142
- query.limit(limit, copy=False)
1143
- if offset:
1144
- query.offset(offset, copy=False)
1145
- return query
1146
-
1147
- def _parse_pipe_syntax_aggregate_fields(self) -> t.Optional[exp.Expression]:
1148
- this = self._parse_assignment()
1149
- if self._match_text_seq("GROUP", "AND", advance=False):
1150
- return this
1151
-
1152
- this = self._parse_alias(this)
1153
-
1154
- if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
1155
- return self._parse_ordered(lambda: this)
1156
-
1157
- return this
1158
-
1159
- def _parse_pipe_syntax_aggregate_group_order_by(
1160
- self, query: exp.Query, group_by_exists: bool = True
1161
- ) -> exp.Query:
1162
- expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
1163
- aggregates_or_groups, orders = [], []
1164
- for element in expr:
1165
- if isinstance(element, exp.Ordered):
1166
- this = element.this
1167
- if isinstance(this, exp.Alias):
1168
- element.set("this", this.args["alias"])
1169
- orders.append(element)
1170
- else:
1171
- this = element
1172
- aggregates_or_groups.append(this)
1173
-
1174
- if group_by_exists and isinstance(query, exp.Select):
1175
- query = query.select(*aggregates_or_groups, copy=False).group_by(
1176
- *[element.args.get("alias", element) for element in aggregates_or_groups],
1177
- copy=False,
1178
- )
1179
- else:
1180
- query = exp.select(*aggregates_or_groups, copy=False).from_(
1181
- query.subquery(copy=False), copy=False
1182
- )
1183
-
1184
- if orders:
1185
- return query.order_by(*orders, copy=False)
1186
-
1187
- return query
1188
-
1189
- def _parse_pipe_syntax_aggregate(self, query: exp.Query) -> exp.Query:
1190
- self._match_text_seq("AGGREGATE")
1191
- query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
1192
-
1193
- if self._match(TokenType.GROUP_BY) or (
1194
- self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
1195
- ):
1196
- return self._parse_pipe_syntax_aggregate_group_order_by(query)
1197
-
1198
- return query
1199
-
1200
- def _parse_pipe_syntax_set_operator(
1201
- self, query: t.Optional[exp.Query]
1202
- ) -> t.Optional[exp.Query]:
1203
- first_setop = self.parse_set_operation(this=query)
1204
-
1205
- if not first_setop or not query:
1206
- return None
1207
-
1208
- first_setop.this.pop()
1209
- distinct = first_setop.args.pop("distinct")
1210
-
1211
- setops = [first_setop.expression.pop(), *self._parse_expressions()]
1212
-
1213
- if isinstance(first_setop, exp.Union):
1214
- return query.union(*setops, distinct=distinct, **first_setop.args)
1215
- if isinstance(first_setop, exp.Except):
1216
- return query.except_(*setops, distinct=distinct, **first_setop.args)
1217
- return query.intersect(*setops, distinct=distinct, **first_setop.args)
1218
-
1219
1131
  def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expression:
1220
1132
  klass = (
1221
1133
  exp.PartitionedByBucket
@@ -1596,6 +1508,9 @@ class Parser(metaclass=_Parser):
1596
1508
  # Whether the 'AS' keyword is optional in the CTE definition syntax
1597
1509
  OPTIONAL_ALIAS_TOKEN_CTE = True
1598
1510
 
1511
+ # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
1512
+ ALTER_RENAME_REQUIRES_COLUMN = True
1513
+
1599
1514
  __slots__ = (
1600
1515
  "error_level",
1601
1516
  "error_message_context",
@@ -1609,6 +1524,7 @@ class Parser(metaclass=_Parser):
1609
1524
  "_next",
1610
1525
  "_prev",
1611
1526
  "_prev_comments",
1527
+ "_pipe_cte_counter",
1612
1528
  )
1613
1529
 
1614
1530
  # Autofilled
@@ -1639,6 +1555,7 @@ class Parser(metaclass=_Parser):
1639
1555
  self._next = None
1640
1556
  self._prev = None
1641
1557
  self._prev_comments = None
1558
+ self._pipe_cte_counter = 0
1642
1559
 
1643
1560
  def parse(
1644
1561
  self, raw_tokens: t.List[Token], sql: t.Optional[str] = None
@@ -3335,9 +3252,11 @@ class Parser(metaclass=_Parser):
3335
3252
  elif self._match(TokenType.VALUES, advance=False):
3336
3253
  this = self._parse_derived_table_values()
3337
3254
  elif from_:
3338
- this = exp.select("*").from_(from_.this, copy=False)
3339
3255
  if self._match(TokenType.PIPE_GT, advance=False):
3340
- return self._parse_pipe_syntax_query(this)
3256
+ return self._parse_pipe_syntax_query(
3257
+ exp.Select().from_(from_.this, append=False, copy=False)
3258
+ )
3259
+ this = exp.select("*").from_(from_.this, copy=False)
3341
3260
  elif self._match(TokenType.SUMMARIZE):
3342
3261
  table = self._match(TokenType.TABLE)
3343
3262
  this = self._parse_select() or self._parse_string() or self._parse_table()
@@ -5203,6 +5122,14 @@ class Parser(metaclass=_Parser):
5203
5122
  exp.DataTypeParam, this=this, expression=self._parse_var(any_token=True)
5204
5123
  )
5205
5124
 
5125
+ def _parse_user_defined_type(self, identifier: exp.Identifier) -> t.Optional[exp.Expression]:
5126
+ type_name = identifier.name
5127
+
5128
+ while self._match(TokenType.DOT):
5129
+ type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
5130
+
5131
+ return exp.DataType.build(type_name, udt=True)
5132
+
5206
5133
  def _parse_types(
5207
5134
  self, check_func: bool = False, schema: bool = False, allow_identifiers: bool = True
5208
5135
  ) -> t.Optional[exp.Expression]:
@@ -5224,12 +5151,7 @@ class Parser(metaclass=_Parser):
5224
5151
  if tokens[0].token_type in self.TYPE_TOKENS:
5225
5152
  self._prev = tokens[0]
5226
5153
  elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
5227
- type_name = identifier.name
5228
-
5229
- while self._match(TokenType.DOT):
5230
- type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
5231
-
5232
- this = exp.DataType.build(type_name, udt=True)
5154
+ this = self._parse_user_defined_type(identifier)
5233
5155
  else:
5234
5156
  self._retreat(self._index - 1)
5235
5157
  return None
@@ -5587,18 +5509,12 @@ class Parser(metaclass=_Parser):
5587
5509
  else:
5588
5510
  field = self._parse_field(any_token=True, anonymous_func=True)
5589
5511
 
5512
+ # Function calls can be qualified, e.g., x.y.FOO()
5513
+ # This converts the final AST to a series of Dots leading to the function call
5514
+ # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
5590
5515
  if isinstance(field, (exp.Func, exp.Window)) and this:
5591
- # BQ & snowflake allow function calls like x.y.count(...), SAFE.SUBSTR(...) etc
5592
- # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
5593
- this = exp.replace_tree(
5594
- this,
5595
- lambda n: (
5596
- self.expression(exp.Dot, this=n.args.get("table"), expression=n.this)
5597
- if n.table
5598
- else n.this
5599
- )
5600
- if isinstance(n, exp.Column)
5601
- else n,
5516
+ this = this.transform(
5517
+ lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
5602
5518
  )
5603
5519
 
5604
5520
  if op:
@@ -5973,7 +5889,11 @@ class Parser(metaclass=_Parser):
5973
5889
  constraints.append(
5974
5890
  self.expression(
5975
5891
  exp.ColumnConstraint,
5976
- kind=exp.ComputedColumnConstraint(this=self._parse_disjunction()),
5892
+ kind=exp.ComputedColumnConstraint(
5893
+ this=self._parse_disjunction(),
5894
+ persisted=self._match_texts(("STORED", "VIRTUAL"))
5895
+ and self._prev.text.upper() == "STORED",
5896
+ ),
5977
5897
  )
5978
5898
  )
5979
5899
 
@@ -7236,23 +7156,6 @@ class Parser(metaclass=_Parser):
7236
7156
 
7237
7157
  return this
7238
7158
 
7239
- def _parse_pipe_syntax_query(self, query: exp.Query) -> t.Optional[exp.Query]:
7240
- while self._match(TokenType.PIPE_GT):
7241
- start = self._curr
7242
- parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(self._curr.text.upper())
7243
- if not parser:
7244
- set_op_query = self._parse_pipe_syntax_set_operator(query)
7245
- if not set_op_query:
7246
- self._retreat(start)
7247
- self.raise_error(f"Unsupported pipe syntax operator: '{start.text.upper()}'.")
7248
- break
7249
-
7250
- query = set_op_query
7251
- else:
7252
- query = parser(self, query)
7253
-
7254
- return query
7255
-
7256
7159
  def _parse_wrapped_id_vars(self, optional: bool = False) -> t.List[exp.Expression]:
7257
7160
  return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
7258
7161
 
@@ -7331,24 +7234,29 @@ class Parser(metaclass=_Parser):
7331
7234
  self._match(TokenType.TABLE)
7332
7235
  return self.expression(exp.Refresh, this=self._parse_string() or self._parse_table())
7333
7236
 
7334
- def _parse_add_column(self) -> t.Optional[exp.Expression]:
7237
+ def _parse_add_column(self) -> t.Optional[exp.ColumnDef]:
7335
7238
  if not self._prev.text.upper() == "ADD":
7336
7239
  return None
7337
7240
 
7241
+ start = self._index
7338
7242
  self._match(TokenType.COLUMN)
7243
+
7339
7244
  exists_column = self._parse_exists(not_=True)
7340
7245
  expression = self._parse_field_def()
7341
7246
 
7342
- if expression:
7343
- expression.set("exists", exists_column)
7247
+ if not isinstance(expression, exp.ColumnDef):
7248
+ self._retreat(start)
7249
+ return None
7344
7250
 
7345
- # https://docs.databricks.com/delta/update-schema.html#explicitly-update-schema-to-add-columns
7346
- if self._match_texts(("FIRST", "AFTER")):
7347
- position = self._prev.text
7348
- column_position = self.expression(
7349
- exp.ColumnPosition, this=self._parse_column(), position=position
7350
- )
7351
- expression.set("position", column_position)
7251
+ expression.set("exists", exists_column)
7252
+
7253
+ # https://docs.databricks.com/delta/update-schema.html#explicitly-update-schema-to-add-columns
7254
+ if self._match_texts(("FIRST", "AFTER")):
7255
+ position = self._prev.text
7256
+ column_position = self.expression(
7257
+ exp.ColumnPosition, this=self._parse_column(), position=position
7258
+ )
7259
+ expression.set("position", column_position)
7352
7260
 
7353
7261
  return expression
7354
7262
 
@@ -7365,13 +7273,24 @@ class Parser(metaclass=_Parser):
7365
7273
  )
7366
7274
 
7367
7275
  def _parse_alter_table_add(self) -> t.List[exp.Expression]:
7368
- def _parse_add_column_or_constraint():
7276
+ def _parse_add_alteration() -> t.Optional[exp.Expression]:
7369
7277
  self._match_text_seq("ADD")
7370
7278
  if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
7371
7279
  return self.expression(
7372
7280
  exp.AddConstraint, expressions=self._parse_csv(self._parse_constraint)
7373
7281
  )
7374
- return self._parse_add_column()
7282
+
7283
+ column_def = self._parse_add_column()
7284
+ if isinstance(column_def, exp.ColumnDef):
7285
+ return column_def
7286
+
7287
+ exists = self._parse_exists(not_=True)
7288
+ if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
7289
+ return self.expression(
7290
+ exp.AddPartition, exists=exists, this=self._parse_field(any_token=True)
7291
+ )
7292
+
7293
+ return None
7375
7294
 
7376
7295
  if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN or self._match_text_seq(
7377
7296
  "COLUMNS"
@@ -7380,7 +7299,7 @@ class Parser(metaclass=_Parser):
7380
7299
 
7381
7300
  return ensure_list(schema) if schema else self._parse_csv(self._parse_field_def)
7382
7301
 
7383
- return self._parse_csv(_parse_add_column_or_constraint)
7302
+ return self._parse_csv(_parse_add_alteration)
7384
7303
 
7385
7304
  def _parse_alter_table_alter(self) -> t.Optional[exp.Expression]:
7386
7305
  if self._match_texts(self.ALTER_ALTER_PARSERS):
@@ -7458,7 +7377,7 @@ class Parser(metaclass=_Parser):
7458
7377
  return self._parse_csv(self._parse_drop_column)
7459
7378
 
7460
7379
  def _parse_alter_table_rename(self) -> t.Optional[exp.AlterRename | exp.RenameColumn]:
7461
- if self._match(TokenType.COLUMN):
7380
+ if self._match(TokenType.COLUMN) or not self.ALTER_RENAME_REQUIRES_COLUMN:
7462
7381
  exists = self._parse_exists()
7463
7382
  old_column = self._parse_column()
7464
7383
  to = self._match_text_seq("TO")
@@ -8408,3 +8327,160 @@ class Parser(metaclass=_Parser):
8408
8327
  expression = self.expression(exp.Identifier, this=token.text, **kwargs)
8409
8328
  expression.update_positions(token)
8410
8329
  return expression
8330
+
8331
+ def _build_pipe_cte(self, query: exp.Query, expressions: t.List[exp.Expression]) -> exp.Select:
8332
+ if not query.selects:
8333
+ query = query.select("*", copy=False)
8334
+
8335
+ self._pipe_cte_counter += 1
8336
+ new_cte = f"__tmp{self._pipe_cte_counter}"
8337
+
8338
+ with_ = query.args.get("with")
8339
+ ctes = with_.pop() if with_ else None
8340
+
8341
+ new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
8342
+ if ctes:
8343
+ new_select.set("with", ctes)
8344
+
8345
+ return new_select.with_(new_cte, as_=query, copy=False)
8346
+
8347
+ def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
8348
+ select = self._parse_select()
8349
+ if not select:
8350
+ return query
8351
+
8352
+ if not query.selects:
8353
+ return self._build_pipe_cte(query.select(*select.expressions), [exp.Star()])
8354
+
8355
+ return self._build_pipe_cte(query, select.expressions)
8356
+
8357
+ def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
8358
+ limit = self._parse_limit()
8359
+ offset = self._parse_offset()
8360
+ if limit:
8361
+ curr_limit = query.args.get("limit", limit)
8362
+ if curr_limit.expression.to_py() >= limit.expression.to_py():
8363
+ query.limit(limit, copy=False)
8364
+ if offset:
8365
+ curr_offset = query.args.get("offset")
8366
+ curr_offset = curr_offset.expression.to_py() if curr_offset else 0
8367
+ query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
8368
+
8369
+ return query
8370
+
8371
+ def _parse_pipe_syntax_aggregate_fields(self) -> t.Optional[exp.Expression]:
8372
+ this = self._parse_assignment()
8373
+ if self._match_text_seq("GROUP", "AND", advance=False):
8374
+ return this
8375
+
8376
+ this = self._parse_alias(this)
8377
+
8378
+ if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
8379
+ return self._parse_ordered(lambda: this)
8380
+
8381
+ return this
8382
+
8383
+ def _parse_pipe_syntax_aggregate_group_order_by(
8384
+ self, query: exp.Select, group_by_exists: bool = True
8385
+ ) -> exp.Select:
8386
+ expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
8387
+ aggregates_or_groups, orders = [], []
8388
+ for element in expr:
8389
+ if isinstance(element, exp.Ordered):
8390
+ this = element.this
8391
+ if isinstance(this, exp.Alias):
8392
+ element.set("this", this.args["alias"])
8393
+ orders.append(element)
8394
+ else:
8395
+ this = element
8396
+ aggregates_or_groups.append(this)
8397
+
8398
+ if group_by_exists:
8399
+ query = query.select(*aggregates_or_groups, copy=False).group_by(
8400
+ *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
8401
+ copy=False,
8402
+ )
8403
+ else:
8404
+ query = query.select(*aggregates_or_groups, copy=False)
8405
+
8406
+ if orders:
8407
+ return query.order_by(*orders, append=False, copy=False)
8408
+
8409
+ return query
8410
+
8411
+ def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
8412
+ self._match_text_seq("AGGREGATE")
8413
+ query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
8414
+
8415
+ if self._match(TokenType.GROUP_BY) or (
8416
+ self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
8417
+ ):
8418
+ query = self._parse_pipe_syntax_aggregate_group_order_by(query)
8419
+
8420
+ return self._build_pipe_cte(query, [exp.Star()])
8421
+
8422
+ def _parse_pipe_syntax_set_operator(
8423
+ self, query: t.Optional[exp.Query]
8424
+ ) -> t.Optional[exp.Select]:
8425
+ first_setop = self.parse_set_operation(this=query)
8426
+
8427
+ if not first_setop or not query:
8428
+ return None
8429
+
8430
+ first_setop.this.pop()
8431
+ distinct = first_setop.args.pop("distinct")
8432
+ setops = [first_setop.expression.pop(), *self._parse_expressions()]
8433
+
8434
+ query = self._build_pipe_cte(query, [exp.Star()])
8435
+ with_ = query.args.get("with")
8436
+ ctes = with_.pop() if with_ else None
8437
+
8438
+ if isinstance(first_setop, exp.Union):
8439
+ query = query.union(*setops, distinct=distinct, copy=False, **first_setop.args)
8440
+ elif isinstance(first_setop, exp.Except):
8441
+ query = query.except_(*setops, distinct=distinct, copy=False, **first_setop.args)
8442
+ else:
8443
+ query = query.intersect(*setops, distinct=distinct, copy=False, **first_setop.args)
8444
+
8445
+ query.set("with", ctes)
8446
+
8447
+ return self._build_pipe_cte(query, [exp.Star()])
8448
+
8449
+ def _parse_pipe_syntax_join(self, query: exp.Select) -> t.Optional[exp.Select]:
8450
+ join = self._parse_join()
8451
+ if not join:
8452
+ return None
8453
+
8454
+ return query.join(join, copy=False)
8455
+
8456
+ def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
8457
+ pivots = self._parse_pivots()
8458
+ if not pivots:
8459
+ return query
8460
+
8461
+ from_ = query.args.get("from")
8462
+ if from_:
8463
+ from_.this.set("pivots", pivots)
8464
+
8465
+ return self._build_pipe_cte(query, [exp.Star()])
8466
+
8467
+ def _parse_pipe_syntax_query(self, query: exp.Select) -> t.Optional[exp.Select]:
8468
+ while self._match(TokenType.PIPE_GT):
8469
+ start = self._curr
8470
+ parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(self._curr.text.upper())
8471
+ if not parser:
8472
+ parsed_query = self._parse_pipe_syntax_set_operator(
8473
+ query
8474
+ ) or self._parse_pipe_syntax_join(query)
8475
+ if not parsed_query:
8476
+ self._retreat(start)
8477
+ self.raise_error(f"Unsupported pipe syntax operator: '{start.text.upper()}'.")
8478
+ break
8479
+ query = parsed_query
8480
+ else:
8481
+ query = parser(self, query)
8482
+
8483
+ if query and not query.selects:
8484
+ return query.select("*", copy=False)
8485
+
8486
+ return query
sqlglot/transforms.py CHANGED
@@ -842,113 +842,122 @@ def struct_kv_to_alias(expression: exp.Expression) -> exp.Expression:
842
842
 
843
843
 
844
844
  def eliminate_join_marks(expression: exp.Expression) -> exp.Expression:
845
- """
846
- Remove join marks from an AST. This rule assumes that all marked columns are qualified.
847
- If this does not hold for a query, consider running `sqlglot.optimizer.qualify` first.
845
+ """https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries006.htm#sthref3178
848
846
 
849
- For example,
850
- SELECT * FROM a, b WHERE a.id = b.id(+) -- ... is converted to
851
- SELECT * FROM a LEFT JOIN b ON a.id = b.id -- this
847
+ 1. You cannot specify the (+) operator in a query block that also contains FROM clause join syntax.
852
848
 
853
- Args:
854
- expression: The AST to remove join marks from.
849
+ 2. The (+) operator can appear only in the WHERE clause or, in the context of left-correlation (that is, when specifying the TABLE clause) in the FROM clause, and can be applied only to a column of a table or view.
855
850
 
856
- Returns:
857
- The AST with join marks removed.
851
+ The (+) operator does not produce an outer join if you specify one table in the outer query and the other table in an inner query.
852
+
853
+ You cannot use the (+) operator to outer-join a table to itself, although self joins are valid.
854
+
855
+ The (+) operator can be applied only to a column, not to an arbitrary expression. However, an arbitrary expression can contain one or more columns marked with the (+) operator.
856
+
857
+ A WHERE condition containing the (+) operator cannot be combined with another condition using the OR logical operator.
858
+
859
+ A WHERE condition cannot use the IN comparison condition to compare a column marked with the (+) operator with an expression.
860
+
861
+ A WHERE condition cannot compare any column marked with the (+) operator with a subquery.
862
+
863
+ -- example with WHERE
864
+ SELECT d.department_name, sum(e.salary) as total_salary
865
+ FROM departments d, employees e
866
+ WHERE e.department_id(+) = d.department_id
867
+ group by department_name
868
+
869
+ -- example of left correlation in select
870
+ SELECT d.department_name, (
871
+ SELECT SUM(e.salary)
872
+ FROM employees e
873
+ WHERE e.department_id(+) = d.department_id) AS total_salary
874
+ FROM departments d;
875
+
876
+ -- example of left correlation in from
877
+ SELECT d.department_name, t.total_salary
878
+ FROM departments d, (
879
+ SELECT SUM(e.salary) AS total_salary
880
+ FROM employees e
881
+ WHERE e.department_id(+) = d.department_id
882
+ ) t
858
883
  """
884
+
859
885
  from sqlglot.optimizer.scope import traverse_scope
886
+ from sqlglot.optimizer.normalize import normalize, normalized
887
+ from collections import defaultdict
860
888
 
861
- for scope in traverse_scope(expression):
889
+ # we go in reverse to check the main query for left correlation
890
+ for scope in reversed(traverse_scope(expression)):
862
891
  query = scope.expression
863
892
 
864
893
  where = query.args.get("where")
865
- joins = query.args.get("joins")
894
+ joins = query.args.get("joins", [])
866
895
 
867
- if not where or not joins:
896
+ # knockout: we do not support left correlation (see point 2)
897
+ assert not scope.is_correlated_subquery, "Correlated queries are not supported"
898
+
899
+ # nothing to do - we check it here after knockout above
900
+ if not where or not any(c.args.get("join_mark") for c in where.find_all(exp.Column)):
868
901
  continue
869
902
 
870
- query_from = query.args["from"]
903
+ # make sure we have AND of ORs to have clear join terms
904
+ where = normalize(where.this)
905
+ assert normalized(where), "Cannot normalize JOIN predicates"
871
906
 
872
- # These keep track of the joins to be replaced
873
- new_joins: t.Dict[str, exp.Join] = {}
874
- old_joins = {join.alias_or_name: join for join in joins}
907
+ joins_ons = defaultdict(list) # dict of {name: list of join AND conditions}
908
+ for cond in [where] if not isinstance(where, exp.And) else where.flatten():
909
+ join_cols = [col for col in cond.find_all(exp.Column) if col.args.get("join_mark")]
875
910
 
876
- for column in scope.columns:
877
- if not column.args.get("join_mark"):
911
+ left_join_table = set(col.table for col in join_cols)
912
+ if not left_join_table:
878
913
  continue
879
914
 
880
- predicate = column.find_ancestor(exp.Predicate, exp.Select)
881
- assert isinstance(
882
- predicate, exp.Binary
883
- ), "Columns can only be marked with (+) when involved in a binary operation"
884
-
885
- predicate_parent = predicate.parent
886
- join_predicate = predicate.pop()
887
-
888
- left_columns = [
889
- c for c in join_predicate.left.find_all(exp.Column) if c.args.get("join_mark")
890
- ]
891
- right_columns = [
892
- c for c in join_predicate.right.find_all(exp.Column) if c.args.get("join_mark")
893
- ]
894
-
895
915
  assert not (
896
- left_columns and right_columns
897
- ), "The (+) marker cannot appear in both sides of a binary predicate"
898
-
899
- marked_column_tables = set()
900
- for col in left_columns or right_columns:
901
- table = col.table
902
- assert table, f"Column {col} needs to be qualified with a table"
916
+ len(left_join_table) > 1
917
+ ), "Cannot combine JOIN predicates from different tables"
903
918
 
919
+ for col in join_cols:
904
920
  col.set("join_mark", False)
905
- marked_column_tables.add(table)
906
921
 
907
- assert (
908
- len(marked_column_tables) == 1
909
- ), "Columns of only a single table can be marked with (+) in a given binary predicate"
910
-
911
- # Add predicate if join already copied, or add join if it is new
912
- join_this = old_joins.get(col.table, query_from).this
913
- existing_join = new_joins.get(join_this.alias_or_name)
914
- if existing_join:
915
- existing_join.set("on", exp.and_(existing_join.args["on"], join_predicate))
916
- else:
917
- new_joins[join_this.alias_or_name] = exp.Join(
918
- this=join_this.copy(), on=join_predicate.copy(), kind="LEFT"
919
- )
922
+ joins_ons[left_join_table.pop()].append(cond)
920
923
 
921
- # If the parent of the target predicate is a binary node, then it now has only one child
922
- if isinstance(predicate_parent, exp.Binary):
923
- if predicate_parent.left is None:
924
- predicate_parent.replace(predicate_parent.right)
925
- else:
926
- predicate_parent.replace(predicate_parent.left)
924
+ old_joins = {join.alias_or_name: join for join in joins}
925
+ new_joins = {}
926
+ query_from = query.args["from"]
927
+
928
+ for table, predicates in joins_ons.items():
929
+ join_what = old_joins.get(table, query_from).this.copy()
930
+ new_joins[join_what.alias_or_name] = exp.Join(
931
+ this=join_what, on=exp.and_(*predicates), kind="LEFT"
932
+ )
927
933
 
928
- only_old_join_sources = old_joins.keys() - new_joins.keys()
934
+ for p in predicates:
935
+ while isinstance(p.parent, exp.Paren):
936
+ p.parent.replace(p)
937
+
938
+ parent = p.parent
939
+ p.pop()
940
+ if isinstance(parent, exp.Binary):
941
+ parent.replace(parent.right if parent.left is None else parent.left)
942
+ elif isinstance(parent, exp.Where):
943
+ parent.pop()
929
944
 
930
945
  if query_from.alias_or_name in new_joins:
946
+ only_old_joins = old_joins.keys() - new_joins.keys()
931
947
  assert (
932
- len(only_old_join_sources) >= 1
948
+ len(only_old_joins) >= 1
933
949
  ), "Cannot determine which table to use in the new FROM clause"
934
950
 
935
- new_from_name = list(only_old_join_sources)[0]
936
- query.set("from", exp.From(this=old_joins.pop(new_from_name).this))
937
- only_old_join_sources.remove(new_from_name)
951
+ new_from_name = list(only_old_joins)[0]
952
+ query.set("from", exp.From(this=old_joins[new_from_name].this))
938
953
 
939
954
  if new_joins:
940
- only_old_join_expressions = []
941
- for old_join_source in only_old_join_sources:
942
- old_join_expression = old_joins[old_join_source]
943
- if not old_join_expression.kind:
944
- old_join_expression.set("kind", "CROSS")
945
-
946
- only_old_join_expressions.append(old_join_expression)
947
-
948
- query.set("joins", list(new_joins.values()) + only_old_join_expressions)
949
-
950
- if not where.this:
951
- where.pop()
955
+ for n, j in old_joins.items(): # preserve any other joins
956
+ if n not in new_joins and n != query.args["from"].name:
957
+ if not j.kind:
958
+ j.set("kind", "CROSS")
959
+ new_joins[n] = j
960
+ query.set("joins", list(new_joins.values()))
952
961
 
953
962
  return expression
954
963
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglot
3
- Version: 26.26.0
3
+ Version: 26.28.1
4
4
  Summary: An easily customizable SQL parser and transpiler
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License: MIT License
@@ -1,22 +1,22 @@
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=YIRxjNMWWorDaNkCL1dWusQPFAthcmiMLy53HgsVsoQ,515
4
+ sqlglot/_version.py,sha256=lTxpjPlB8VNbv3452Opk2GFByRI5SLtHItxl9sne84Q,515
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=vVF2fwwgwo2TWmgpBU4_kYz28xVZ7hhSoaIfpbeyMck,242714
8
- sqlglot/generator.py,sha256=4JFrruvOtWB1vM470PzTNQC2yGG_7XOiogAjEukuhek,212229
7
+ sqlglot/expressions.py,sha256=oE7OmkFEstTWoPqM7yCls2I2JNyia8Spr-jVi3n77-A,242992
8
+ sqlglot/generator.py,sha256=4iJ0BxkzinmosIhfhb34xjxaFpzw3Zo7fvmknaf5uRs,212432
9
9
  sqlglot/helper.py,sha256=9nZjFVRBtMKFC3EdzpDQ6jkazFO19po6BF8xHiNGZIo,15111
10
10
  sqlglot/jsonpath.py,sha256=dKdI3PNINNGimmSse2IIv-GbPN_3lXncXh_70QH7Lss,7664
11
11
  sqlglot/lineage.py,sha256=kXBDSErmZZluZx_kkrMj4MPEOAbkvcbX1tbOW7Bpl-U,15303
12
- sqlglot/parser.py,sha256=Qz6C1DARQxi60XGcAicOtUdb9K6RNEH2Fe689s0jrzA,317671
12
+ sqlglot/parser.py,sha256=TksM9cVq6bbbyM0sgglcOb-p6_1_Xk6EPIS2Buj-048,320530
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=DQVJ95WrIvhYfe02Ytb4NQug2aMwDCEwpMBW1LKDqzE,2031
17
17
  sqlglot/time.py,sha256=Q62gv6kL40OiRBF6BMESxKJcMVn7ZLNw7sv8H34z5FI,18400
18
18
  sqlglot/tokens.py,sha256=R0B8GQSbQ9GoDc0NlaT5Tc8RjgEOx2IYIkYU5rY8Rg8,48742
19
- sqlglot/transforms.py,sha256=iTwRPMHTyRx_RG25ItSOnigw_v2tnG9cgwMq0Nwcy2U,39778
19
+ sqlglot/transforms.py,sha256=3jpbHeVTLK9hmQi5f3_vmK-5jZB32_ittCkO7poxCs4,40631
20
20
  sqlglot/trie.py,sha256=v27uXMrHfqrXlJ6GmeTSMovsB_3o0ctnlKhdNt7W6fI,2245
21
21
  sqlglot/dialects/__init__.py,sha256=aZTLpe2SwgWqiVrRabmfV8TVLPVHFydGwb_zhcVhRss,3499
22
22
  sqlglot/dialects/athena.py,sha256=xjy75ej0T3douCUfFKhE1I3kqvPEuQY29x24WG1--Vw,6307
@@ -29,19 +29,19 @@ sqlglot/dialects/drill.py,sha256=FOh7_KjPx_77pv0DiHKZog0CcmzqeF9_PEmGnJ1ESSM,582
29
29
  sqlglot/dialects/druid.py,sha256=kh3snZtneehNOWqs3XcPjsrhNaRbkCQ8E4hHbWJ1fHM,690
30
30
  sqlglot/dialects/duckdb.py,sha256=alEYXBW5uUApRC8IRYnsapeiJq7JJwUmrK18C56RYsg,47780
31
31
  sqlglot/dialects/dune.py,sha256=gALut-fFfN2qMsr8LvZ1NQK3F3W9z2f4PwMvTMXVVVg,375
32
- sqlglot/dialects/hive.py,sha256=IKAM2elf_n3LgRcPK_4-JuE1j6shd6FhE1QJvBaP55U,31665
32
+ sqlglot/dialects/hive.py,sha256=PO6DLT1kHL-U2kFfV1CsNgQFT7A32LuGN71gnTXEOfY,31728
33
33
  sqlglot/dialects/materialize.py,sha256=_DPLPt8YrdQIIXNrGJw1IMcGOoAEJ9NO9X9pDfy4hxs,3494
34
- sqlglot/dialects/mysql.py,sha256=PnhqX2B15J71WUROefPTc7ZOP0vybbkZGWIDrxYN5Dc,48159
34
+ sqlglot/dialects/mysql.py,sha256=prZecn3zeoifZX7l54UuLG64ar7I-or_z9lF-rT8bds,49233
35
35
  sqlglot/dialects/oracle.py,sha256=llxu2LzndrsGyceTod-Leh03vuPWEUKzVHB5gQY-tY8,15313
36
- sqlglot/dialects/postgres.py,sha256=p3sKOp1ImqsQkSz_BWP8pTTMasPFXDJscnMXccXN_5U,30594
36
+ sqlglot/dialects/postgres.py,sha256=KUyMoLkm1_sZKUbdjn6bjXx9xz7sbEMKa-fl5Mzfrsk,31025
37
37
  sqlglot/dialects/presto.py,sha256=ltKbQ44efeq1HM0T8Qq0rsBSx6B6bF9RoKtUBVeoz70,33155
38
38
  sqlglot/dialects/prql.py,sha256=OF2LfDb4uzKIF7kpCfpL5G7VP1pnzLbjfW5QFUnuPvo,7803
39
39
  sqlglot/dialects/redshift.py,sha256=H8H8lGizHIAd4qLoPeFchyiGZKO1I8U_B058woukuGw,15366
40
40
  sqlglot/dialects/risingwave.py,sha256=hwEOPjMw0ZM_3fjQcBUE00oy6I8V6mzYOOYmcwwS8mw,2898
41
- sqlglot/dialects/snowflake.py,sha256=qbrtxaBrpJNIaBth1kkVe6ep5TZktMN3lcWEp1-m0hs,61467
41
+ sqlglot/dialects/snowflake.py,sha256=m4Gekw4NhoD3q4WF1TJhetRmmwkh8XG9Rqq8mL3P31E,61761
42
42
  sqlglot/dialects/spark.py,sha256=fbmiTKAQiKqG9yE_HAxYGgQiOjdxB9tJyjOtgdqF100,7645
43
43
  sqlglot/dialects/spark2.py,sha256=8er7nHDm5Wc57m9AOxKN0sd_DVzbhAL44H_udlFh9O8,14258
44
- sqlglot/dialects/sqlite.py,sha256=ZhVkBzse5yh5pxyYN_daU6CMr4Yint4pIoH3n4gbnns,12394
44
+ sqlglot/dialects/sqlite.py,sha256=UzJwIdY1PsLArMxNt5lKvk8COHvXeo4FoqW41LqVmM8,12440
45
45
  sqlglot/dialects/starrocks.py,sha256=fHNgvq5Nz7dI4QUWCTOO5VDOYjasBxRRlcg9TbY0UZE,11235
46
46
  sqlglot/dialects/tableau.py,sha256=oIawDzUITxGCWaEMB8OaNMPWhbC3U-2y09pYPm4eazc,2190
47
47
  sqlglot/dialects/teradata.py,sha256=xWa-9kSTsT-eM1NePi_oIM1dPHmXW89GLU5Uda3_6Ao,14036
@@ -69,11 +69,11 @@ sqlglot/optimizer/pushdown_projections.py,sha256=7NoK5NAUVYVhs0YnYyo6WuXfaO-BShS
69
69
  sqlglot/optimizer/qualify.py,sha256=oAPfwub7dEkrlCrsptcJWpLya4BgKhN6M5SwIs_86LY,4002
70
70
  sqlglot/optimizer/qualify_columns.py,sha256=X2Iydssan_Fw84cd-mrzqxG3eRfRdpP6HVRofSbfHlg,40515
71
71
  sqlglot/optimizer/qualify_tables.py,sha256=5f5enBAh-bpNB9ewF97W9fx9h1TGXj1Ih5fncvH42sY,6486
72
- sqlglot/optimizer/scope.py,sha256=Fqz9GpBqO1GWzRAnqdflXXNz44ot_1JqVBC-DnYAU_E,30063
72
+ sqlglot/optimizer/scope.py,sha256=lZWJsR1k-vx1VdxOn0yvbF_LcviXbK357WlrgOLXGEs,30123
73
73
  sqlglot/optimizer/simplify.py,sha256=S0Blqg5Mq2KRRWhWz-Eivch9sBjBhg9fRJA6EdBzj2g,50704
74
74
  sqlglot/optimizer/unnest_subqueries.py,sha256=kzWUVDlxs8z9nmRx-8U-pHXPtVZhEIwkKqmKhr2QLvc,10908
75
- sqlglot-26.26.0.dist-info/licenses/LICENSE,sha256=AI3__mHZfOtzY3EluR_pIYBm3_pE7TbVx7qaHxoZ114,1065
76
- sqlglot-26.26.0.dist-info/METADATA,sha256=7uj_XRkGPWpSUuokcBiin3WhwIBUs8rRcwSyx1zszbY,20732
77
- sqlglot-26.26.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
- sqlglot-26.26.0.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
79
- sqlglot-26.26.0.dist-info/RECORD,,
75
+ sqlglot-26.28.1.dist-info/licenses/LICENSE,sha256=AI3__mHZfOtzY3EluR_pIYBm3_pE7TbVx7qaHxoZ114,1065
76
+ sqlglot-26.28.1.dist-info/METADATA,sha256=ElrNZkPPdEmAmU1gVJgndWkFCWlhnYqLLkGB4562Bd4,20732
77
+ sqlglot-26.28.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
+ sqlglot-26.28.1.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
79
+ sqlglot-26.28.1.dist-info/RECORD,,