sqlglot 27.8.0__py3-none-any.whl → 27.10.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/expressions.py CHANGED
@@ -2468,6 +2468,10 @@ class Grant(Expression):
2468
2468
  }
2469
2469
 
2470
2470
 
2471
+ class Revoke(Expression):
2472
+ arg_types = {**Grant.arg_types, "cascade": False}
2473
+
2474
+
2471
2475
  class Group(Expression):
2472
2476
  arg_types = {
2473
2477
  "expressions": False,
@@ -2790,6 +2794,11 @@ class BackupProperty(Property):
2790
2794
  arg_types = {"this": True}
2791
2795
 
2792
2796
 
2797
+ # https://doris.apache.org/docs/sql-manual/sql-statements/table-and-view/async-materialized-view/CREATE-ASYNC-MATERIALIZED-VIEW/
2798
+ class BuildProperty(Property):
2799
+ arg_types = {"this": True}
2800
+
2801
+
2793
2802
  class BlockCompressionProperty(Property):
2794
2803
  arg_types = {
2795
2804
  "autotemp": False,
@@ -3031,6 +3040,27 @@ class PartitionByRangePropertyDynamic(Expression):
3031
3040
  arg_types = {"this": False, "start": True, "end": True, "every": True}
3032
3041
 
3033
3042
 
3043
+ # https://doris.apache.org/docs/table-design/data-partitioning/manual-partitioning
3044
+ class PartitionByListProperty(Property):
3045
+ arg_types = {"partition_expressions": True, "create_expressions": True}
3046
+
3047
+
3048
+ # https://doris.apache.org/docs/table-design/data-partitioning/manual-partitioning
3049
+ class PartitionList(Expression):
3050
+ arg_types = {"this": True, "expressions": True}
3051
+
3052
+
3053
+ # https://doris.apache.org/docs/sql-manual/sql-statements/table-and-view/async-materialized-view/CREATE-ASYNC-MATERIALIZED-VIEW
3054
+ class RefreshTriggerProperty(Property):
3055
+ arg_types = {
3056
+ "method": True,
3057
+ "kind": False,
3058
+ "every": False,
3059
+ "unit": False,
3060
+ "starts": False,
3061
+ }
3062
+
3063
+
3034
3064
  # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3035
3065
  class UniqueKeyProperty(Property):
3036
3066
  arg_types = {"expressions": True}
@@ -4304,7 +4334,14 @@ class Select(Query):
4304
4334
 
4305
4335
  @property
4306
4336
  def named_selects(self) -> t.List[str]:
4307
- return [e.output_name for e in self.expressions if e.alias_or_name]
4337
+ selects = []
4338
+
4339
+ for e in self.expressions:
4340
+ if e.alias_or_name:
4341
+ selects.append(e.output_name)
4342
+ elif isinstance(e, Aliases):
4343
+ selects.extend([a.name for a in e.aliases])
4344
+ return selects
4308
4345
 
4309
4346
  @property
4310
4347
  def is_star(self) -> bool:
@@ -4876,6 +4913,7 @@ class Alter(Expression):
4876
4913
  "options": False,
4877
4914
  "cluster": False,
4878
4915
  "not_valid": False,
4916
+ "check": False,
4879
4917
  }
4880
4918
 
4881
4919
  @property
@@ -5429,6 +5467,11 @@ class ByteLength(Func):
5429
5467
  pass
5430
5468
 
5431
5469
 
5470
+ # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#bool_for_json
5471
+ class JSONBool(Func):
5472
+ pass
5473
+
5474
+
5432
5475
  class ArrayRemove(Func):
5433
5476
  arg_types = {"this": True, "expression": True}
5434
5477
 
@@ -5455,10 +5498,28 @@ class ApproxTopK(AggFunc):
5455
5498
  arg_types = {"this": True, "expression": False, "counters": False}
5456
5499
 
5457
5500
 
5501
+ class ApproxTopSum(AggFunc):
5502
+ arg_types = {"this": True, "expression": True, "count": True}
5503
+
5504
+
5505
+ class ApproxQuantiles(AggFunc):
5506
+ arg_types = {"this": True, "expression": False}
5507
+
5508
+
5509
+ class FarmFingerprint(Func):
5510
+ arg_types = {"expressions": True}
5511
+ is_var_len_args = True
5512
+ _sql_names = ["FARM_FINGERPRINT", "FARMFINGERPRINT64"]
5513
+
5514
+
5458
5515
  class Flatten(Func):
5459
5516
  pass
5460
5517
 
5461
5518
 
5519
+ class Float64(Func):
5520
+ arg_types = {"this": True, "expression": False}
5521
+
5522
+
5462
5523
  # https://spark.apache.org/docs/latest/api/sql/index.html#transform
5463
5524
  class Transform(Func):
5464
5525
  arg_types = {"this": True, "expression": True}
@@ -5548,6 +5609,10 @@ class ToChar(Func):
5548
5609
  }
5549
5610
 
5550
5611
 
5612
+ class ToCodePoints(Func):
5613
+ pass
5614
+
5615
+
5551
5616
  # https://docs.snowflake.com/en/sql-reference/functions/to_decimal
5552
5617
  # https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TO_NUMBER.html
5553
5618
  class ToNumber(Func):
@@ -5568,6 +5633,10 @@ class ToDouble(Func):
5568
5633
  }
5569
5634
 
5570
5635
 
5636
+ class CodePointsToBytes(Func):
5637
+ pass
5638
+
5639
+
5571
5640
  class Columns(Func):
5572
5641
  arg_types = {"this": True, "unpack": False}
5573
5642
 
@@ -5877,8 +5946,9 @@ class ConcatWs(Concat):
5877
5946
  _sql_names = ["CONCAT_WS"]
5878
5947
 
5879
5948
 
5949
+ # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#contains_substr
5880
5950
  class Contains(Func):
5881
- arg_types = {"this": True, "expression": True}
5951
+ arg_types = {"this": True, "expression": True, "json_scope": False}
5882
5952
 
5883
5953
 
5884
5954
  # https://docs.oracle.com/cd/B13789_01/server.101/b10759/operators004.htm#i1035022
@@ -5933,7 +6003,7 @@ class DateAdd(Func, IntervalOp):
5933
6003
 
5934
6004
 
5935
6005
  class DateBin(Func, IntervalOp):
5936
- arg_types = {"this": True, "expression": True, "unit": False, "zone": False}
6006
+ arg_types = {"this": True, "expression": True, "unit": False, "zone": False, "origin": False}
5937
6007
 
5938
6008
 
5939
6009
  class DateSub(Func, IntervalOp):
@@ -6193,12 +6263,16 @@ class Floor(Func):
6193
6263
  arg_types = {"this": True, "decimals": False, "to": False}
6194
6264
 
6195
6265
 
6266
+ class FromBase32(Func):
6267
+ pass
6268
+
6269
+
6196
6270
  class FromBase64(Func):
6197
6271
  pass
6198
6272
 
6199
6273
 
6200
- class FeaturesAtTime(Func):
6201
- arg_types = {"this": True, "time": False, "num_rows": False, "ignore_feature_nulls": False}
6274
+ class ToBase32(Func):
6275
+ pass
6202
6276
 
6203
6277
 
6204
6278
  class ToBase64(Func):
@@ -6553,9 +6627,18 @@ class JSONFormat(Func):
6553
6627
 
6554
6628
  # https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#operator_member-of
6555
6629
  class JSONArrayContains(Binary, Predicate, Func):
6630
+ arg_types = {"this": True, "expression": True, "json_type": False}
6556
6631
  _sql_names = ["JSON_ARRAY_CONTAINS"]
6557
6632
 
6558
6633
 
6634
+ class ParseBignumeric(Func):
6635
+ pass
6636
+
6637
+
6638
+ class ParseNumeric(Func):
6639
+ pass
6640
+
6641
+
6559
6642
  class ParseJSON(Func):
6560
6643
  # BigQuery, Snowflake have PARSE_JSON, Presto has JSON_PARSE
6561
6644
  # Snowflake also has TRY_PARSE_JSON, which is represented using `safe`
@@ -6714,7 +6797,7 @@ class Nvl2(Func):
6714
6797
 
6715
6798
 
6716
6799
  class Normalize(Func):
6717
- arg_types = {"this": True, "form": False}
6800
+ arg_types = {"this": True, "form": False, "is_casefold": False}
6718
6801
 
6719
6802
 
6720
6803
  class Overlay(Func):
@@ -6726,6 +6809,29 @@ class Predict(Func):
6726
6809
  arg_types = {"this": True, "expression": True, "params_struct": False}
6727
6810
 
6728
6811
 
6812
+ # https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-feature-time
6813
+ class FeaturesAtTime(Func):
6814
+ arg_types = {"this": True, "time": False, "num_rows": False, "ignore_feature_nulls": False}
6815
+
6816
+
6817
+ # https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-embedding
6818
+ class GenerateEmbedding(Func):
6819
+ arg_types = {"this": True, "expression": True, "params_struct": False}
6820
+
6821
+
6822
+ # https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#vector_search
6823
+ class VectorSearch(Func):
6824
+ arg_types = {
6825
+ "this": True,
6826
+ "column_to_search": True,
6827
+ "query_table": True,
6828
+ "query_column_to_search": False,
6829
+ "top_k": False,
6830
+ "distance_type": False,
6831
+ "options": False,
6832
+ }
6833
+
6834
+
6729
6835
  class Pow(Binary, Func):
6730
6836
  _sql_names = ["POWER", "POW"]
6731
6837
 
@@ -6743,7 +6849,13 @@ class Quantile(AggFunc):
6743
6849
 
6744
6850
 
6745
6851
  class ApproxQuantile(Quantile):
6746
- arg_types = {"this": True, "quantile": True, "accuracy": False, "weight": False}
6852
+ arg_types = {
6853
+ "this": True,
6854
+ "quantile": True,
6855
+ "accuracy": False,
6856
+ "weight": False,
6857
+ "error_tolerance": False,
6858
+ }
6747
6859
 
6748
6860
 
6749
6861
  class Quarter(Func):
@@ -6845,6 +6957,10 @@ class SafeDivide(Func):
6845
6957
  arg_types = {"this": True, "expression": True}
6846
6958
 
6847
6959
 
6960
+ class SafeConvertBytesToString(Func):
6961
+ pass
6962
+
6963
+
6848
6964
  class SHA(Func):
6849
6965
  _sql_names = ["SHA", "SHA1"]
6850
6966
 
sqlglot/generator.py CHANGED
@@ -1137,14 +1137,14 @@ class Generator(metaclass=_Generator):
1137
1137
  if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1138
1138
  exp.Properties.Location.POST_WITH
1139
1139
  ):
1140
- properties_sql = self.sql(
1141
- exp.Properties(
1142
- expressions=[
1143
- *properties_locs[exp.Properties.Location.POST_SCHEMA],
1144
- *properties_locs[exp.Properties.Location.POST_WITH],
1145
- ]
1146
- )
1140
+ props_ast = exp.Properties(
1141
+ expressions=[
1142
+ *properties_locs[exp.Properties.Location.POST_SCHEMA],
1143
+ *properties_locs[exp.Properties.Location.POST_WITH],
1144
+ ]
1147
1145
  )
1146
+ props_ast.parent = expression
1147
+ properties_sql = self.sql(props_ast)
1148
1148
 
1149
1149
  if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1150
1150
  properties_sql = self.sep() + properties_sql
@@ -1670,8 +1670,14 @@ class Generator(metaclass=_Generator):
1670
1670
  elif p_loc == exp.Properties.Location.POST_SCHEMA:
1671
1671
  root_properties.append(p)
1672
1672
 
1673
- root_props = self.root_properties(exp.Properties(expressions=root_properties))
1674
- with_props = self.with_properties(exp.Properties(expressions=with_properties))
1673
+ root_props_ast = exp.Properties(expressions=root_properties)
1674
+ root_props_ast.parent = expression.parent
1675
+
1676
+ with_props_ast = exp.Properties(expressions=with_properties)
1677
+ with_props_ast.parent = expression.parent
1678
+
1679
+ root_props = self.root_properties(root_props_ast)
1680
+ with_props = self.with_properties(with_props_ast)
1675
1681
 
1676
1682
  if root_props and with_props and not self.pretty:
1677
1683
  with_props = " " + with_props
@@ -2992,7 +2998,19 @@ class Generator(metaclass=_Generator):
2992
2998
  args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2993
2999
 
2994
3000
  if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2995
- args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
3001
+
3002
+ def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3003
+ if not e.type:
3004
+ from sqlglot.optimizer.annotate_types import annotate_types
3005
+
3006
+ e = annotate_types(e, dialect=self.dialect)
3007
+
3008
+ if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3009
+ return e
3010
+
3011
+ return exp.func("coalesce", e, exp.Literal.string(""))
3012
+
3013
+ args = [_wrap_with_coalesce(e) for e in args]
2996
3014
 
2997
3015
  return args
2998
3016
 
@@ -3543,8 +3561,9 @@ class Generator(metaclass=_Generator):
3543
3561
  options = f", {options}" if options else ""
3544
3562
  kind = self.sql(expression, "kind")
3545
3563
  not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3564
+ check = " WITH CHECK" if expression.args.get("check") else ""
3546
3565
 
3547
- return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3566
+ return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}"
3548
3567
 
3549
3568
  def add_column_sql(self, expression: exp.Expression) -> str:
3550
3569
  sql = self.sql(expression)
@@ -4029,8 +4048,10 @@ class Generator(metaclass=_Generator):
4029
4048
  return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4030
4049
 
4031
4050
  # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4032
- def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
4033
- return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
4051
+ def uniquekeyproperty_sql(
4052
+ self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4053
+ ) -> str:
4054
+ return f"{prefix} ({self.expressions(expression, flat=True)})"
4034
4055
 
4035
4056
  # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4036
4057
  def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
@@ -4162,6 +4183,47 @@ class Generator(metaclass=_Generator):
4162
4183
  parameters = self.sql(expression, "params_struct")
4163
4184
  return self.func("PREDICT", model, table, parameters or None)
4164
4185
 
4186
+ def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4187
+ model = self.sql(expression, "this")
4188
+ model = f"MODEL {model}"
4189
+ table = self.sql(expression, "expression")
4190
+ table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4191
+ parameters = self.sql(expression, "params_struct")
4192
+ return self.func("GENERATE_EMBEDDING", model, table, parameters or None)
4193
+
4194
+ def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4195
+ this_sql = self.sql(expression, "this")
4196
+ if isinstance(expression.this, exp.Table):
4197
+ this_sql = f"TABLE {this_sql}"
4198
+
4199
+ return self.func(
4200
+ "FEATURES_AT_TIME",
4201
+ this_sql,
4202
+ expression.args.get("time"),
4203
+ expression.args.get("num_rows"),
4204
+ expression.args.get("ignore_feature_nulls"),
4205
+ )
4206
+
4207
+ def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4208
+ this_sql = self.sql(expression, "this")
4209
+ if isinstance(expression.this, exp.Table):
4210
+ this_sql = f"TABLE {this_sql}"
4211
+
4212
+ query_table = self.sql(expression, "query_table")
4213
+ if isinstance(expression.args["query_table"], exp.Table):
4214
+ query_table = f"TABLE {query_table}"
4215
+
4216
+ return self.func(
4217
+ "VECTOR_SEARCH",
4218
+ this_sql,
4219
+ expression.args.get("column_to_search"),
4220
+ query_table,
4221
+ expression.args.get("query_column_to_search"),
4222
+ expression.args.get("top_k"),
4223
+ expression.args.get("distance_type"),
4224
+ expression.args.get("options"),
4225
+ )
4226
+
4165
4227
  def forin_sql(self, expression: exp.ForIn) -> str:
4166
4228
  this = self.sql(expression, "this")
4167
4229
  expression_sql = self.sql(expression, "expression")
@@ -4741,7 +4803,14 @@ class Generator(metaclass=_Generator):
4741
4803
 
4742
4804
  return f"{this} APPLY({expr})"
4743
4805
 
4744
- def grant_sql(self, expression: exp.Grant) -> str:
4806
+ def _grant_or_revoke_sql(
4807
+ self,
4808
+ expression: exp.Grant | exp.Revoke,
4809
+ keyword: str,
4810
+ preposition: str,
4811
+ grant_option_prefix: str = "",
4812
+ grant_option_suffix: str = "",
4813
+ ) -> str:
4745
4814
  privileges_sql = self.expressions(expression, key="privileges", flat=True)
4746
4815
 
4747
4816
  kind = self.sql(expression, "kind")
@@ -4752,9 +4821,30 @@ class Generator(metaclass=_Generator):
4752
4821
 
4753
4822
  principals = self.expressions(expression, key="principals", flat=True)
4754
4823
 
4755
- grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4824
+ if not expression.args.get("grant_option"):
4825
+ grant_option_prefix = grant_option_suffix = ""
4826
+
4827
+ # cascade for revoke only
4828
+ cascade = self.sql(expression, "cascade")
4829
+ cascade = f" {cascade}" if cascade else ""
4830
+
4831
+ return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
4756
4832
 
4757
- return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4833
+ def grant_sql(self, expression: exp.Grant) -> str:
4834
+ return self._grant_or_revoke_sql(
4835
+ expression,
4836
+ keyword="GRANT",
4837
+ preposition="TO",
4838
+ grant_option_suffix=" WITH GRANT OPTION",
4839
+ )
4840
+
4841
+ def revoke_sql(self, expression: exp.Revoke) -> str:
4842
+ return self._grant_or_revoke_sql(
4843
+ expression,
4844
+ keyword="REVOKE",
4845
+ preposition="FROM",
4846
+ grant_option_prefix="GRANT OPTION FOR ",
4847
+ )
4758
4848
 
4759
4849
  def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4760
4850
  this = self.sql(expression, "this")
@@ -4869,19 +4959,6 @@ class Generator(metaclass=_Generator):
4869
4959
  value = f" {value}" if value else ""
4870
4960
  return f"{this}{value}"
4871
4961
 
4872
- def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4873
- this_sql = self.sql(expression, "this")
4874
- if isinstance(expression.this, exp.Table):
4875
- this_sql = f"TABLE {this_sql}"
4876
-
4877
- return self.func(
4878
- "FEATURES_AT_TIME",
4879
- this_sql,
4880
- expression.args.get("time"),
4881
- expression.args.get("num_rows"),
4882
- expression.args.get("ignore_feature_nulls"),
4883
- )
4884
-
4885
4962
  def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4886
4963
  return (
4887
4964
  f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
@@ -5151,3 +5228,20 @@ class Generator(metaclass=_Generator):
5151
5228
 
5152
5229
  def space_sql(self: Generator, expression: exp.Space) -> str:
5153
5230
  return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5231
+
5232
+ def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5233
+ return f"BUILD {self.sql(expression, 'this')}"
5234
+
5235
+ def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5236
+ method = self.sql(expression, "method")
5237
+ kind = expression.args.get("kind")
5238
+ if not kind:
5239
+ return f"REFRESH {method}"
5240
+
5241
+ every = self.sql(expression, "every")
5242
+ unit = self.sql(expression, "unit")
5243
+ every = f" EVERY {every} {unit}" if every else ""
5244
+ starts = self.sql(expression, "starts")
5245
+ starts = f" STARTS {starts}" if starts else ""
5246
+
5247
+ return f"REFRESH {method} ON {kind}{every}{starts}"
@@ -853,7 +853,7 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expression) -> None:
853
853
  if isinstance(selection, exp.Subquery):
854
854
  if not selection.output_name:
855
855
  selection.set("alias", exp.TableAlias(this=exp.to_identifier(f"_col_{i}")))
856
- elif not isinstance(selection, exp.Alias) and not selection.is_star:
856
+ elif not isinstance(selection, (exp.Alias, exp.Aliases)) and not selection.is_star:
857
857
  selection = alias(
858
858
  selection,
859
859
  alias=selection.output_name or f"_col_{i}",
@@ -304,6 +304,7 @@ class Scope:
304
304
  isinstance(ancestor, (exp.Order, exp.Distinct))
305
305
  and (
306
306
  isinstance(ancestor.parent, (exp.Window, exp.WithinGroup))
307
+ or not isinstance(ancestor.parent, exp.Select)
307
308
  or column.name not in named_selects
308
309
  )
309
310
  )
sqlglot/parser.py CHANGED
@@ -844,6 +844,7 @@ class Parser(metaclass=_Parser):
844
844
  TokenType.DESCRIBE: lambda self: self._parse_describe(),
845
845
  TokenType.DROP: lambda self: self._parse_drop(),
846
846
  TokenType.GRANT: lambda self: self._parse_grant(),
847
+ TokenType.REVOKE: lambda self: self._parse_revoke(),
847
848
  TokenType.INSERT: lambda self: self._parse_insert(),
848
849
  TokenType.KILL: lambda self: self._parse_kill(),
849
850
  TokenType.LOAD: lambda self: self._parse_load(),
@@ -1244,7 +1245,6 @@ class Parser(metaclass=_Parser):
1244
1245
  "OPENJSON": lambda self: self._parse_open_json(),
1245
1246
  "OVERLAY": lambda self: self._parse_overlay(),
1246
1247
  "POSITION": lambda self: self._parse_position(),
1247
- "PREDICT": lambda self: self._parse_predict(),
1248
1248
  "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
1249
1249
  "STRING_AGG": lambda self: self._parse_string_agg(),
1250
1250
  "SUBSTRING": lambda self: self._parse_substring(),
@@ -3533,10 +3533,17 @@ class Parser(metaclass=_Parser):
3533
3533
 
3534
3534
  while True:
3535
3535
  if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
3536
- parser = self.QUERY_MODIFIER_PARSERS[self._curr.token_type]
3536
+ modifier_token = self._curr
3537
+ parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
3537
3538
  key, expression = parser(self)
3538
3539
 
3539
3540
  if expression:
3541
+ if this.args.get(key):
3542
+ self.raise_error(
3543
+ f"Found multiple '{modifier_token.text.upper()}' clauses",
3544
+ token=modifier_token,
3545
+ )
3546
+
3540
3547
  this.set(key, expression)
3541
3548
  if key == "limit":
3542
3549
  offset = expression.args.pop("offset", None)
@@ -6914,20 +6921,6 @@ class Parser(metaclass=_Parser):
6914
6921
  exp.StrPosition, this=haystack, substr=needle, position=seq_get(args, 2)
6915
6922
  )
6916
6923
 
6917
- def _parse_predict(self) -> exp.Predict:
6918
- self._match_text_seq("MODEL")
6919
- this = self._parse_table()
6920
-
6921
- self._match(TokenType.COMMA)
6922
- self._match_text_seq("TABLE")
6923
-
6924
- return self.expression(
6925
- exp.Predict,
6926
- this=this,
6927
- expression=self._parse_table(),
6928
- params_struct=self._match(TokenType.COMMA) and self._parse_bitwise(),
6929
- )
6930
-
6931
6924
  def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
6932
6925
  args = self._parse_csv(self._parse_table)
6933
6926
  return exp.JoinHint(this=func_name.upper(), expressions=args)
@@ -7597,6 +7590,7 @@ class Parser(metaclass=_Parser):
7597
7590
  exists = self._parse_exists()
7598
7591
  only = self._match_text_seq("ONLY")
7599
7592
  this = self._parse_table(schema=True)
7593
+ check = self._match_text_seq("WITH", "CHECK")
7600
7594
  cluster = self._parse_on_property() if self._match(TokenType.ON) else None
7601
7595
 
7602
7596
  if self._next:
@@ -7619,6 +7613,7 @@ class Parser(metaclass=_Parser):
7619
7613
  options=options,
7620
7614
  cluster=cluster,
7621
7615
  not_valid=not_valid,
7616
+ check=check,
7622
7617
  )
7623
7618
 
7624
7619
  return self._parse_as_command(start)
@@ -8416,9 +8411,9 @@ class Parser(metaclass=_Parser):
8416
8411
 
8417
8412
  return self.expression(exp.GrantPrincipal, this=principal, kind=kind)
8418
8413
 
8419
- def _parse_grant(self) -> exp.Grant | exp.Command:
8420
- start = self._prev
8421
-
8414
+ def _parse_grant_revoke_common(
8415
+ self,
8416
+ ) -> t.Tuple[t.Optional[t.List], t.Optional[str], t.Optional[exp.Expression]]:
8422
8417
  privileges = self._parse_csv(self._parse_grant_privilege)
8423
8418
 
8424
8419
  self._match(TokenType.ON)
@@ -8428,6 +8423,13 @@ class Parser(metaclass=_Parser):
8428
8423
  # such as "foo.*", "*.*" which are not easily parseable yet
8429
8424
  securable = self._try_parse(self._parse_table_parts)
8430
8425
 
8426
+ return privileges, kind, securable
8427
+
8428
+ def _parse_grant(self) -> exp.Grant | exp.Command:
8429
+ start = self._prev
8430
+
8431
+ privileges, kind, securable = self._parse_grant_revoke_common()
8432
+
8431
8433
  if not securable or not self._match_text_seq("TO"):
8432
8434
  return self._parse_as_command(start)
8433
8435
 
@@ -8447,6 +8449,35 @@ class Parser(metaclass=_Parser):
8447
8449
  grant_option=grant_option,
8448
8450
  )
8449
8451
 
8452
+ def _parse_revoke(self) -> exp.Revoke | exp.Command:
8453
+ start = self._prev
8454
+
8455
+ grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
8456
+
8457
+ privileges, kind, securable = self._parse_grant_revoke_common()
8458
+
8459
+ if not securable or not self._match_text_seq("FROM"):
8460
+ return self._parse_as_command(start)
8461
+
8462
+ principals = self._parse_csv(self._parse_grant_principal)
8463
+
8464
+ cascade = None
8465
+ if self._match_texts(("CASCADE", "RESTRICT")):
8466
+ cascade = self._prev.text.upper()
8467
+
8468
+ if self._curr:
8469
+ return self._parse_as_command(start)
8470
+
8471
+ return self.expression(
8472
+ exp.Revoke,
8473
+ privileges=privileges,
8474
+ kind=kind,
8475
+ securable=securable,
8476
+ principals=principals,
8477
+ grant_option=grant_option,
8478
+ cascade=cascade,
8479
+ )
8480
+
8450
8481
  def _parse_overlay(self) -> exp.Overlay:
8451
8482
  return self.expression(
8452
8483
  exp.Overlay,
@@ -8722,3 +8753,36 @@ class Parser(metaclass=_Parser):
8722
8753
  returning=returning,
8723
8754
  on_condition=self._parse_on_condition(),
8724
8755
  )
8756
+
8757
+ def _parse_group_concat(self) -> t.Optional[exp.Expression]:
8758
+ def concat_exprs(
8759
+ node: t.Optional[exp.Expression], exprs: t.List[exp.Expression]
8760
+ ) -> exp.Expression:
8761
+ if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
8762
+ concat_exprs = [
8763
+ self.expression(exp.Concat, expressions=node.expressions, safe=True)
8764
+ ]
8765
+ node.set("expressions", concat_exprs)
8766
+ return node
8767
+ if len(exprs) == 1:
8768
+ return exprs[0]
8769
+ return self.expression(exp.Concat, expressions=args, safe=True)
8770
+
8771
+ args = self._parse_csv(self._parse_lambda)
8772
+
8773
+ if args:
8774
+ order = args[-1] if isinstance(args[-1], exp.Order) else None
8775
+
8776
+ if order:
8777
+ # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
8778
+ # remove 'expr' from exp.Order and add it back to args
8779
+ args[-1] = order.this
8780
+ order.set("this", concat_exprs(order.this, args))
8781
+
8782
+ this = order or concat_exprs(args[0], args)
8783
+ else:
8784
+ this = None
8785
+
8786
+ separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
8787
+
8788
+ return self.expression(exp.GroupConcat, this=this, separator=separator)
sqlglot/tokens.py CHANGED
@@ -376,6 +376,7 @@ class TokenType(AutoName):
376
376
  RENAME = auto()
377
377
  REPLACE = auto()
378
378
  RETURNING = auto()
379
+ REVOKE = auto()
379
380
  REFERENCES = auto()
380
381
  RIGHT = auto()
381
382
  RLIKE = auto()
@@ -972,6 +973,7 @@ class Tokenizer(metaclass=_Tokenizer):
972
973
  "COMMENT": TokenType.COMMENT,
973
974
  "EXPLAIN": TokenType.COMMAND,
974
975
  "GRANT": TokenType.GRANT,
976
+ "REVOKE": TokenType.REVOKE,
975
977
  "OPTIMIZE": TokenType.COMMAND,
976
978
  "PREPARE": TokenType.COMMAND,
977
979
  "VACUUM": TokenType.COMMAND,