sqlglot 27.16.3__py3-none-any.whl → 27.18.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.16.3'
32
- __version_tuple__ = version_tuple = (27, 16, 3)
31
+ __version__ = version = '27.18.0'
32
+ __version_tuple__ = version_tuple = (27, 18, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -88,6 +88,7 @@ DIALECTS = [
88
88
  "RisingWave",
89
89
  "SingleStore",
90
90
  "Snowflake",
91
+ "Solr",
91
92
  "Spark",
92
93
  "Spark2",
93
94
  "SQLite",
@@ -332,7 +332,6 @@ class ClickHouse(Dialect):
332
332
  "PARSEDATETIME": _build_datetime_format(exp.ParseDatetime),
333
333
  "RANDCANONICAL": exp.Rand.from_arg_list,
334
334
  "STR_TO_DATE": _build_str_to_date,
335
- "TUPLE": exp.Struct.from_arg_list,
336
335
  "TIMESTAMP_SUB": build_date_delta(exp.TimestampSub, default_unit=None),
337
336
  "TIMESTAMPSUB": build_date_delta(exp.TimestampSub, default_unit=None),
338
337
  "TIMESTAMP_ADD": build_date_delta(exp.TimestampAdd, default_unit=None),
@@ -505,14 +504,13 @@ class ClickHouse(Dialect):
505
504
  }
506
505
  )(AGG_FUNCTIONS, AGG_FUNCTIONS_SUFFIXES)
507
506
 
508
- FUNCTIONS_WITH_ALIASED_ARGS = {*parser.Parser.FUNCTIONS_WITH_ALIASED_ARGS, "TUPLE"}
509
-
510
507
  FUNCTION_PARSERS = {
511
508
  **parser.Parser.FUNCTION_PARSERS,
512
509
  "ARRAYJOIN": lambda self: self.expression(exp.Explode, this=self._parse_expression()),
513
510
  "QUANTILE": lambda self: self._parse_quantile(),
514
511
  "MEDIAN": lambda self: self._parse_quantile(),
515
512
  "COLUMNS": lambda self: self._parse_columns(),
513
+ "TUPLE": lambda self: exp.Struct.from_arg_list(self._parse_function_args(alias=True)),
516
514
  }
517
515
 
518
516
  FUNCTION_PARSERS.pop("MATCH")
@@ -1126,6 +1124,7 @@ class ClickHouse(Dialect):
1126
1124
  exp.RegexpLike: lambda self, e: self.func("match", e.this, e.expression),
1127
1125
  exp.Rand: rename_func("randCanonical"),
1128
1126
  exp.StartsWith: rename_func("startsWith"),
1127
+ exp.Struct: rename_func("tuple"),
1129
1128
  exp.EndsWith: rename_func("endsWith"),
1130
1129
  exp.EuclideanDistance: rename_func("L2Distance"),
1131
1130
  exp.StrPosition: lambda self, e: strposition_sql(
@@ -99,6 +99,7 @@ class Dialects(str, Enum):
99
99
  REDSHIFT = "redshift"
100
100
  RISINGWAVE = "risingwave"
101
101
  SNOWFLAKE = "snowflake"
102
+ SOLR = "solr"
102
103
  SPARK = "spark"
103
104
  SPARK2 = "spark2"
104
105
  SQLITE = "sqlite"
@@ -291,6 +292,12 @@ class _Dialect(type):
291
292
  TokenType.SEMI,
292
293
  }
293
294
 
295
+ klass.VALID_INTERVAL_UNITS = {
296
+ *klass.VALID_INTERVAL_UNITS,
297
+ *klass.DATE_PART_MAPPING.keys(),
298
+ *klass.DATE_PART_MAPPING.values(),
299
+ }
300
+
294
301
  return klass
295
302
 
296
303
 
@@ -551,6 +558,8 @@ class Dialect(metaclass=_Dialect):
551
558
  IDENTIFIER_START = '"'
552
559
  IDENTIFIER_END = '"'
553
560
 
561
+ VALID_INTERVAL_UNITS: t.Set[str] = set()
562
+
554
563
  # Delimiters for bit, hex, byte and unicode literals
555
564
  BIT_START: t.Optional[str] = None
556
565
  BIT_END: t.Optional[str] = None
@@ -1296,3 +1296,9 @@ class DuckDB(Dialect):
1296
1296
  return self.sql(exp.Cast(this=func, to=this.type))
1297
1297
 
1298
1298
  return self.sql(func)
1299
+
1300
+ def format_sql(self, expression: exp.Format) -> str:
1301
+ if expression.name.lower() == "%s" and len(expression.expressions) == 1:
1302
+ return self.func("FORMAT", "'{}'", expression.expressions[0])
1303
+
1304
+ return self.function_fallback_sql(expression)
sqlglot/dialects/mysql.py CHANGED
@@ -179,6 +179,21 @@ class MySQL(Dialect):
179
179
  "%W": "%A",
180
180
  }
181
181
 
182
+ VALID_INTERVAL_UNITS = {
183
+ *Dialect.VALID_INTERVAL_UNITS,
184
+ "SECOND_MICROSECOND",
185
+ "MINUTE_MICROSECOND",
186
+ "MINUTE_SECOND",
187
+ "HOUR_MICROSECOND",
188
+ "HOUR_SECOND",
189
+ "HOUR_MINUTE",
190
+ "DAY_MICROSECOND",
191
+ "DAY_SECOND",
192
+ "DAY_MINUTE",
193
+ "DAY_HOUR",
194
+ "YEAR_MONTH",
195
+ }
196
+
182
197
  class Tokenizer(tokens.Tokenizer):
183
198
  QUOTES = ["'", '"']
184
199
  COMMENTS = ["--", "#", ("/*", "*/")]
@@ -375,6 +375,8 @@ class Postgres(Dialect):
375
375
  VAR_SINGLE_TOKENS = {"$"}
376
376
 
377
377
  class Parser(parser.Parser):
378
+ SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = True
379
+
378
380
  PROPERTY_PARSERS = {
379
381
  **parser.Parser.PROPERTY_PARSERS,
380
382
  "SET": lambda self: self.expression(exp.SetConfigProperty, this=self._parse_set()),
@@ -426,7 +428,7 @@ class Postgres(Dialect):
426
428
  "DATE_PART": lambda self: self._parse_date_part(),
427
429
  "JSON_AGG": lambda self: self.expression(
428
430
  exp.JSONArrayAgg,
429
- this=self._parse_bitwise(),
431
+ this=self._parse_lambda(),
430
432
  order=self._parse_order(),
431
433
  ),
432
434
  "JSONB_EXISTS": lambda self: self._parse_jsonb_exists(),
@@ -324,6 +324,15 @@ def _build_regexp_extract(expr_type: t.Type[E]) -> t.Callable[[t.List], E]:
324
324
  return _builder
325
325
 
326
326
 
327
+ def _build_like(expr_type: t.Type[E]) -> t.Callable[[t.List], E | exp.Escape]:
328
+ def _builder(args: t.List) -> E | exp.Escape:
329
+ like_expr = expr_type(this=args[0], expression=args[1])
330
+ escape = seq_get(args, 2)
331
+ return exp.Escape(this=like_expr, expression=escape) if escape else like_expr
332
+
333
+ return _builder
334
+
335
+
327
336
  def _regexpextract_sql(self, expression: exp.RegexpExtract | exp.RegexpExtractAll) -> str:
328
337
  # Other dialects don't support all of the following parameters, so we need to
329
338
  # generate default values as necessary to ensure the transpilation is correct
@@ -522,14 +531,27 @@ class Snowflake(Dialect):
522
531
  **Dialect.TYPE_TO_EXPRESSIONS,
523
532
  exp.DataType.Type.INT: {
524
533
  *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.INT],
534
+ exp.Ascii,
525
535
  exp.Length,
536
+ exp.BitLength,
537
+ exp.Levenshtein,
538
+ exp.JarowinklerSimilarity,
526
539
  },
527
540
  exp.DataType.Type.VARCHAR: {
528
541
  *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.VARCHAR],
542
+ exp.Base64DecodeString,
543
+ exp.Base64Encode,
544
+ exp.DecompressString,
529
545
  exp.MD5,
530
546
  exp.AIAgg,
531
547
  exp.AIClassify,
532
548
  exp.AISummarizeAgg,
549
+ exp.Chr,
550
+ exp.Collate,
551
+ exp.Collation,
552
+ exp.HexDecodeString,
553
+ exp.HexEncode,
554
+ exp.Initcap,
533
555
  exp.RegexpExtract,
534
556
  exp.RegexpReplace,
535
557
  exp.Repeat,
@@ -541,9 +563,13 @@ class Snowflake(Dialect):
541
563
  },
542
564
  exp.DataType.Type.BINARY: {
543
565
  *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.BINARY],
566
+ exp.Base64DecodeBinary,
567
+ exp.Compress,
568
+ exp.DecompressBinary,
544
569
  exp.MD5Digest,
545
570
  exp.SHA1Digest,
546
571
  exp.SHA2Digest,
572
+ exp.Unhex,
547
573
  },
548
574
  exp.DataType.Type.BIGINT: {
549
575
  *Dialect.TYPE_TO_EXPRESSIONS[exp.DataType.Type.BIGINT],
@@ -566,7 +592,9 @@ class Snowflake(Dialect):
566
592
  expr_type: lambda self, e: self._annotate_by_args(e, "this")
567
593
  for expr_type in (
568
594
  exp.Left,
595
+ exp.Pad,
569
596
  exp.Right,
597
+ exp.Stuff,
570
598
  exp.Substring,
571
599
  )
572
600
  },
@@ -678,6 +706,7 @@ class Snowflake(Dialect):
678
706
  "DATE_TRUNC": _date_trunc_to_time,
679
707
  "DATEADD": _build_date_time_add(exp.DateAdd),
680
708
  "DATEDIFF": _build_datediff,
709
+ "DAYOFWEEKISO": exp.DayOfWeekIso.from_arg_list,
681
710
  "DIV0": _build_if_from_div0,
682
711
  "EDITDISTANCE": lambda args: exp.Levenshtein(
683
712
  this=seq_get(args, 0), expression=seq_get(args, 1), max_dist=seq_get(args, 2)
@@ -746,6 +775,8 @@ class Snowflake(Dialect):
746
775
  "TO_JSON": exp.JSONFormat.from_arg_list,
747
776
  "VECTOR_L2_DISTANCE": exp.EuclideanDistance.from_arg_list,
748
777
  "ZEROIFNULL": _build_if_from_zeroifnull,
778
+ "LIKE": _build_like(exp.Like),
779
+ "ILIKE": _build_like(exp.ILike),
749
780
  }
750
781
  FUNCTIONS.pop("PREDICT")
751
782
 
@@ -1478,13 +1509,23 @@ class Snowflake(Dialect):
1478
1509
 
1479
1510
  def datatype_sql(self, expression: exp.DataType) -> str:
1480
1511
  expressions = expression.expressions
1481
- if (
1482
- expressions
1483
- and expression.is_type(*exp.DataType.STRUCT_TYPES)
1484
- and any(isinstance(field_type, exp.DataType) for field_type in expressions)
1485
- ):
1486
- # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ]
1487
- return "OBJECT"
1512
+ if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES):
1513
+ for field_type in expressions:
1514
+ # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ]
1515
+ if isinstance(field_type, exp.DataType):
1516
+ return "OBJECT"
1517
+ if (
1518
+ isinstance(field_type, exp.ColumnDef)
1519
+ and field_type.this
1520
+ and field_type.this.is_string
1521
+ ):
1522
+ # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides
1523
+ # converting 'foo' into an identifier, we also need to quote it because these
1524
+ # keys are case-sensitive. For example:
1525
+ #
1526
+ # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct
1527
+ # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL
1528
+ field_type.this.replace(exp.to_identifier(field_type.name, quoted=True))
1488
1529
 
1489
1530
  return super().datatype_sql(expression)
1490
1531
 
@@ -1816,3 +1857,9 @@ class Snowflake(Dialect):
1816
1857
 
1817
1858
  def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
1818
1859
  return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}"
1860
+
1861
+ def format_sql(self, expression: exp.Format) -> str:
1862
+ if expression.name.lower() == "%s" and len(expression.expressions) == 1:
1863
+ return self.func("TO_CHAR", expression.expressions[0])
1864
+
1865
+ return self.function_fallback_sql(expression)
@@ -0,0 +1,22 @@
1
+ from sqlglot import exp, parser, tokens
2
+ from sqlglot.dialects.dialect import Dialect, NormalizationStrategy
3
+ from sqlglot.tokens import TokenType
4
+
5
+
6
+ # https://solr.apache.org/guide/solr/latest/query-guide/sql-query.html
7
+
8
+
9
+ class Solr(Dialect):
10
+ NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE
11
+ DPIPE_IS_STRING_CONCAT = False
12
+ SUPPORTS_SEMI_ANTI_JOIN = False
13
+
14
+ class Parser(parser.Parser):
15
+ DISJUNCTION = {
16
+ **parser.Parser.DISJUNCTION,
17
+ TokenType.DPIPE: exp.Or,
18
+ }
19
+
20
+ class Tokenizer(tokens.Tokenizer):
21
+ QUOTES = ["'"]
22
+ IDENTIFIERS = ["`"]
sqlglot/expressions.py CHANGED
@@ -5531,6 +5531,10 @@ class EuclideanDistance(Func):
5531
5531
  arg_types = {"this": True, "expression": True}
5532
5532
 
5533
5533
 
5534
+ class JarowinklerSimilarity(Func):
5535
+ arg_types = {"this": True, "expression": True}
5536
+
5537
+
5534
5538
  class AggFunc(Func):
5535
5539
  pass
5536
5540
 
@@ -6022,6 +6026,10 @@ class Collate(Binary, Func):
6022
6026
  pass
6023
6027
 
6024
6028
 
6029
+ class Collation(Func):
6030
+ pass
6031
+
6032
+
6025
6033
  class Ceil(Func):
6026
6034
  arg_types = {"this": True, "decimals": False, "to": False}
6027
6035
  _sql_names = ["CEIL", "CEILING"]
@@ -6414,6 +6422,19 @@ class ToBase64(Func):
6414
6422
  pass
6415
6423
 
6416
6424
 
6425
+ # https://docs.snowflake.com/en/sql-reference/functions/base64_decode_binary
6426
+ class Base64DecodeBinary(Func):
6427
+ arg_types = {"this": True, "alphabet": False}
6428
+
6429
+
6430
+ class Base64DecodeString(Func):
6431
+ arg_types = {"this": True, "alphabet": False}
6432
+
6433
+
6434
+ class Base64Encode(Func):
6435
+ arg_types = {"this": True, "max_line_length": False, "alphabet": False}
6436
+
6437
+
6417
6438
  # https://trino.io/docs/current/functions/datetime.html#from_iso8601_timestamp
6418
6439
  class FromISO8601Timestamp(Func):
6419
6440
  _sql_names = ["FROM_ISO8601_TIMESTAMP"]
@@ -6465,6 +6486,32 @@ class Hex(Func):
6465
6486
  pass
6466
6487
 
6467
6488
 
6489
+ # https://docs.snowflake.com/en/sql-reference/functions/hex_decode_string
6490
+ class HexDecodeString(Func):
6491
+ pass
6492
+
6493
+
6494
+ # https://docs.snowflake.com/en/sql-reference/functions/hex_encode
6495
+ class HexEncode(Func):
6496
+ arg_types = {"this": True, "case": False}
6497
+
6498
+
6499
+ # T-SQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/compress-transact-sql?view=sql-server-ver17
6500
+ # Snowflake: https://docs.snowflake.com/en/sql-reference/functions/compress
6501
+ class Compress(Func):
6502
+ arg_types = {"this": True, "method": False}
6503
+
6504
+
6505
+ # Snowflake: https://docs.snowflake.com/en/sql-reference/functions/decompress_binary
6506
+ class DecompressBinary(Func):
6507
+ arg_types = {"this": True, "method": True}
6508
+
6509
+
6510
+ # Snowflake: https://docs.snowflake.com/en/sql-reference/functions/decompress_string
6511
+ class DecompressString(Func):
6512
+ arg_types = {"this": True, "method": True}
6513
+
6514
+
6468
6515
  class LowerHex(Hex):
6469
6516
  pass
6470
6517
 
@@ -6871,6 +6918,10 @@ class Length(Func):
6871
6918
  _sql_names = ["LENGTH", "LEN", "CHAR_LENGTH", "CHARACTER_LENGTH"]
6872
6919
 
6873
6920
 
6921
+ class BitLength(Func):
6922
+ pass
6923
+
6924
+
6874
6925
  class Levenshtein(Func):
6875
6926
  arg_types = {
6876
6927
  "this": True,
@@ -8557,6 +8608,19 @@ def parse_identifier(name: str | Identifier, dialect: DialectType = None) -> Ide
8557
8608
 
8558
8609
  INTERVAL_STRING_RE = re.compile(r"\s*(-?[0-9]+(?:\.[0-9]+)?)\s*([a-zA-Z]+)\s*")
8559
8610
 
8611
+ # Matches day-time interval strings that contain
8612
+ # - A number of days (possibly negative or with decimals)
8613
+ # - At least one space
8614
+ # - Portions of a time-like signature, potentially negative
8615
+ # - Standard format [-]h+:m+:s+[.f+]
8616
+ # - Just minutes/seconds/frac seconds [-]m+:s+.f+
8617
+ # - Just hours, minutes, maybe colon [-]h+:m+[:]
8618
+ # - Just hours, maybe colon [-]h+[:]
8619
+ # - Just colon :
8620
+ INTERVAL_DAY_TIME_RE = re.compile(
8621
+ r"\s*-?\s*\d+(?:\.\d+)?\s+(?:-?(?:\d+:)?\d+:\d+(?:\.\d+)?|-?(?:\d+:){1,2}|:)\s*"
8622
+ )
8623
+
8560
8624
 
8561
8625
  def to_interval(interval: str | Literal) -> Interval:
8562
8626
  """Builds an interval expression from a string like '1 day' or '5 months'."""
sqlglot/generator.py CHANGED
@@ -727,6 +727,7 @@ class Generator(metaclass=_Generator):
727
727
  "dialect",
728
728
  "unsupported_messages",
729
729
  "_escaped_quote_end",
730
+ "_escaped_byte_quote_end",
730
731
  "_escaped_identifier_end",
731
732
  "_next_name",
732
733
  "_identifier_start",
@@ -773,6 +774,11 @@ class Generator(metaclass=_Generator):
773
774
  self._escaped_quote_end: str = (
774
775
  self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
775
776
  )
777
+ self._escaped_byte_quote_end: str = (
778
+ self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
779
+ if self.dialect.BYTE_END
780
+ else ""
781
+ )
776
782
  self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
777
783
 
778
784
  self._next_name = name_sequence("_t")
@@ -1376,7 +1382,13 @@ class Generator(metaclass=_Generator):
1376
1382
  def bytestring_sql(self, expression: exp.ByteString) -> str:
1377
1383
  this = self.sql(expression, "this")
1378
1384
  if self.dialect.BYTE_START:
1379
- return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1385
+ escaped_byte_string = self.escape_str(
1386
+ this,
1387
+ escape_backslash=False,
1388
+ delimiter=self.dialect.BYTE_END,
1389
+ escaped_delimiter=self._escaped_byte_quote_end,
1390
+ )
1391
+ return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1380
1392
  return this
1381
1393
 
1382
1394
  def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
@@ -2475,16 +2487,23 @@ class Generator(metaclass=_Generator):
2475
2487
  text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2476
2488
  return text
2477
2489
 
2478
- def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2490
+ def escape_str(
2491
+ self,
2492
+ text: str,
2493
+ escape_backslash: bool = True,
2494
+ delimiter: t.Optional[str] = None,
2495
+ escaped_delimiter: t.Optional[str] = None,
2496
+ ) -> str:
2479
2497
  if self.dialect.ESCAPED_SEQUENCES:
2480
2498
  to_escaped = self.dialect.ESCAPED_SEQUENCES
2481
2499
  text = "".join(
2482
2500
  to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2483
2501
  )
2484
2502
 
2485
- return self._replace_line_breaks(text).replace(
2486
- self.dialect.QUOTE_END, self._escaped_quote_end
2487
- )
2503
+ delimiter = delimiter or self.dialect.QUOTE_END
2504
+ escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2505
+
2506
+ return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2488
2507
 
2489
2508
  def loaddata_sql(self, expression: exp.LoadData) -> str:
2490
2509
  local = " LOCAL" if expression.args.get("local") else ""
@@ -3256,14 +3275,19 @@ class Generator(metaclass=_Generator):
3256
3275
  return f"(SELECT {self.sql(unnest)})"
3257
3276
 
3258
3277
  def interval_sql(self, expression: exp.Interval) -> str:
3259
- unit = self.sql(expression, "unit")
3278
+ unit_expression = expression.args.get("unit")
3279
+ unit = self.sql(unit_expression) if unit_expression else ""
3260
3280
  if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3261
3281
  unit = self.TIME_PART_SINGULARS.get(unit, unit)
3262
3282
  unit = f" {unit}" if unit else ""
3263
3283
 
3264
3284
  if self.SINGLE_STRING_INTERVAL:
3265
3285
  this = expression.this.name if expression.this else ""
3266
- return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3286
+ if this:
3287
+ if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3288
+ return f"INTERVAL '{this}'{unit}"
3289
+ return f"INTERVAL '{this}{unit}'"
3290
+ return f"INTERVAL{unit}"
3267
3291
 
3268
3292
  this = self.sql(expression, "this")
3269
3293
  if this:
@@ -1588,7 +1588,7 @@ class Gen:
1588
1588
  kvs = []
1589
1589
  arg_types = list(node.arg_types)[arg_index:] if arg_index else node.arg_types
1590
1590
 
1591
- for k in arg_types or arg_types:
1591
+ for k in arg_types:
1592
1592
  v = node.args.get(k)
1593
1593
 
1594
1594
  if v is not None:
sqlglot/parser.py CHANGED
@@ -1561,6 +1561,10 @@ class Parser(metaclass=_Parser):
1561
1561
  # Adding an ON TRUE, makes transpilation semantically correct for other dialects
1562
1562
  ADD_JOIN_ON_TRUE = False
1563
1563
 
1564
+ # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
1565
+ # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
1566
+ SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = False
1567
+
1564
1568
  __slots__ = (
1565
1569
  "error_level",
1566
1570
  "error_message_context",
@@ -3119,21 +3123,26 @@ class Parser(metaclass=_Parser):
3119
3123
  )
3120
3124
 
3121
3125
  def _parse_update(self) -> exp.Update:
3122
- this = self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS)
3123
- expressions = self._match(TokenType.SET) and self._parse_csv(self._parse_equality)
3124
- returning = self._parse_returning()
3125
- return self.expression(
3126
- exp.Update,
3127
- **{ # type: ignore
3128
- "this": this,
3129
- "expressions": expressions,
3130
- "from": self._parse_from(joins=True),
3131
- "where": self._parse_where(),
3132
- "returning": returning or self._parse_returning(),
3133
- "order": self._parse_order(),
3134
- "limit": self._parse_limit(),
3135
- },
3136
- )
3126
+ kwargs: t.Dict[str, t.Any] = {
3127
+ "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
3128
+ }
3129
+ while self._curr:
3130
+ if self._match(TokenType.SET):
3131
+ kwargs["expressions"] = self._parse_csv(self._parse_equality)
3132
+ elif self._match(TokenType.RETURNING, advance=False):
3133
+ kwargs["returning"] = self._parse_returning()
3134
+ elif self._match(TokenType.FROM, advance=False):
3135
+ kwargs["from"] = self._parse_from(joins=True)
3136
+ elif self._match(TokenType.WHERE, advance=False):
3137
+ kwargs["where"] = self._parse_where()
3138
+ elif self._match(TokenType.ORDER_BY, advance=False):
3139
+ kwargs["order"] = self._parse_order()
3140
+ elif self._match(TokenType.LIMIT, advance=False):
3141
+ kwargs["limit"] = self._parse_limit()
3142
+ else:
3143
+ break
3144
+
3145
+ return self.expression(exp.Update, **kwargs)
3137
3146
 
3138
3147
  def _parse_use(self) -> exp.Use:
3139
3148
  return self.expression(
@@ -4236,9 +4245,11 @@ class Parser(metaclass=_Parser):
4236
4245
  )
4237
4246
 
4238
4247
  def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]:
4239
- if not self._match(TokenType.UNNEST):
4248
+ if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
4240
4249
  return None
4241
4250
 
4251
+ self._advance()
4252
+
4242
4253
  expressions = self._parse_wrapped_csv(self._parse_equality)
4243
4254
  offset = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
4244
4255
 
@@ -4613,7 +4624,7 @@ class Parser(metaclass=_Parser):
4613
4624
 
4614
4625
  def _parse_grouping_set(self) -> t.Optional[exp.Expression]:
4615
4626
  if self._match(TokenType.L_PAREN):
4616
- grouping_set = self._parse_csv(self._parse_column)
4627
+ grouping_set = self._parse_csv(self._parse_bitwise)
4617
4628
  self._match_r_paren()
4618
4629
  return self.expression(exp.Tuple, expressions=grouping_set)
4619
4630
 
@@ -5100,14 +5111,43 @@ class Parser(metaclass=_Parser):
5100
5111
  isinstance(this, exp.Column)
5101
5112
  and not this.table
5102
5113
  and not this.this.quoted
5103
- and this.name.upper() in ("IS", "ROWS")
5114
+ and self._curr
5115
+ and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
5104
5116
  ):
5105
5117
  self._retreat(index)
5106
5118
  return None
5107
5119
 
5108
- unit = self._parse_function() or (
5109
- not self._match(TokenType.ALIAS, advance=False)
5110
- and self._parse_var(any_token=True, upper=True)
5120
+ # handle day-time format interval span with omitted units:
5121
+ # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
5122
+ interval_span_units_omitted = None
5123
+ if (
5124
+ this
5125
+ and this.is_string
5126
+ and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
5127
+ and exp.INTERVAL_DAY_TIME_RE.match(this.name)
5128
+ ):
5129
+ index = self._index
5130
+
5131
+ # Var "TO" Var
5132
+ first_unit = self._parse_var(any_token=True, upper=True)
5133
+ second_unit = None
5134
+ if first_unit and self._match_text_seq("TO"):
5135
+ second_unit = self._parse_var(any_token=True, upper=True)
5136
+
5137
+ interval_span_units_omitted = not (first_unit and second_unit)
5138
+
5139
+ self._retreat(index)
5140
+
5141
+ unit = (
5142
+ None
5143
+ if interval_span_units_omitted
5144
+ else (
5145
+ self._parse_function()
5146
+ or (
5147
+ not self._match(TokenType.ALIAS, advance=False)
5148
+ and self._parse_var(any_token=True, upper=True)
5149
+ )
5150
+ )
5111
5151
  )
5112
5152
 
5113
5153
  # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
@@ -5124,6 +5164,7 @@ class Parser(metaclass=_Parser):
5124
5164
  if len(parts) == 1:
5125
5165
  this = exp.Literal.string(parts[0][0])
5126
5166
  unit = self.expression(exp.Var, this=parts[0][1].upper())
5167
+
5127
5168
  if self.INTERVAL_SPANS and self._match_text_seq("TO"):
5128
5169
  unit = self.expression(
5129
5170
  exp.IntervalSpan, this=unit, expression=self._parse_var(any_token=True, upper=True)
@@ -5490,6 +5531,11 @@ class Parser(metaclass=_Parser):
5490
5531
 
5491
5532
  type_token = unsigned_type_token or type_token
5492
5533
 
5534
+ # NULLABLE without parentheses can be a column (Presto/Trino)
5535
+ if type_token == TokenType.NULLABLE and not expressions:
5536
+ self._retreat(index)
5537
+ return None
5538
+
5493
5539
  this = exp.DataType(
5494
5540
  this=exp.DataType.Type[type_token.value],
5495
5541
  expressions=expressions,
@@ -5760,6 +5806,10 @@ class Parser(metaclass=_Parser):
5760
5806
  this.add_comments(comments)
5761
5807
 
5762
5808
  self._match_r_paren(expression=this)
5809
+
5810
+ if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
5811
+ return self._parse_window(this)
5812
+
5763
5813
  return this
5764
5814
 
5765
5815
  def _parse_primary(self) -> t.Optional[exp.Expression]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglot
3
- Version: 27.16.3
3
+ Version: 27.18.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
@@ -39,7 +39,7 @@ Dynamic: provides-extra
39
39
 
40
40
  ![SQLGlot logo](sqlglot.png)
41
41
 
42
- SQLGlot is a no-dependency SQL parser, transpiler, optimizer, and engine. It can be used to format SQL or translate between [30 different dialects](https://github.com/tobymao/sqlglot/blob/main/sqlglot/dialects/__init__.py) like [DuckDB](https://duckdb.org/), [Presto](https://prestodb.io/) / [Trino](https://trino.io/), [Spark](https://spark.apache.org/) / [Databricks](https://www.databricks.com/), [Snowflake](https://www.snowflake.com/en/), and [BigQuery](https://cloud.google.com/bigquery/). It aims to read a wide variety of SQL inputs and output syntactically and semantically correct SQL in the targeted dialects.
42
+ SQLGlot is a no-dependency SQL parser, transpiler, optimizer, and engine. It can be used to format SQL or translate between [31 different dialects](https://github.com/tobymao/sqlglot/blob/main/sqlglot/dialects/__init__.py) like [DuckDB](https://duckdb.org/), [Presto](https://prestodb.io/) / [Trino](https://trino.io/), [Spark](https://spark.apache.org/) / [Databricks](https://www.databricks.com/), [Snowflake](https://www.snowflake.com/en/), and [BigQuery](https://cloud.google.com/bigquery/). It aims to read a wide variety of SQL inputs and output syntactically and semantically correct SQL in the targeted dialects.
43
43
 
44
44
  It is a very comprehensive generic SQL parser with a robust [test suite](https://github.com/tobymao/sqlglot/blob/main/tests/). It is also quite [performant](#benchmarks), while being written purely in Python.
45
45
 
@@ -613,6 +613,7 @@ x + interval '1' month
613
613
  | RisingWave | Community |
614
614
  | SingleStore | Community |
615
615
  | Snowflake | Official |
616
+ | Solr | Community |
616
617
  | Spark | Official |
617
618
  | SQLite | Official |
618
619
  | StarRocks | Official |
@@ -1,15 +1,15 @@
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=BtxvTN4ZIXvi5Rf3fZO9Rg6s_6zmGod22E1mXw7N_x8,708
4
+ sqlglot/_version.py,sha256=7UxZQRE12m08A5rXKriqghWBW14eGYzdfIFlvSEPOcI,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=Qp0u8vmU9M7AV-iAqjd_0EQJQsO3VjyyHvZ4sQfQZdo,256153
8
- sqlglot/generator.py,sha256=Twp2rdS57BxztRQx9z6d_e-GOAd4hW0UeSnywiIrbws,225487
7
+ sqlglot/expressions.py,sha256=KEMqwokmUV8udKzoUAQlkjZJXV-FkwAJLmSOIpGt4_k,258058
8
+ sqlglot/generator.py,sha256=ZHFzi6_EOJl5V-dnz71QJOIwtxj66oJdF6tUQoxBpck,226436
9
9
  sqlglot/helper.py,sha256=9nZjFVRBtMKFC3EdzpDQ6jkazFO19po6BF8xHiNGZIo,15111
10
10
  sqlglot/jsonpath.py,sha256=SQgaxzaEYBN7At9dkTK4N1Spk6xHxvHL6QtCIP6iM30,7905
11
11
  sqlglot/lineage.py,sha256=Qj5ykuDNcATppb9vOjoIKBqRVLbu3OMPiZk9f3iyv40,15312
12
- sqlglot/parser.py,sha256=Zzu2OOp5z9mvUrD7niyK6iIPZODLsL41pJnAjAgNtLQ,335392
12
+ sqlglot/parser.py,sha256=wlw73WjzXAHC9AH7Ezkfyeh134P97HheLPueCgXk4Mc,337292
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
@@ -18,31 +18,32 @@ sqlglot/time.py,sha256=Q62gv6kL40OiRBF6BMESxKJcMVn7ZLNw7sv8H34z5FI,18400
18
18
  sqlglot/tokens.py,sha256=M6E-2vbIs41CYwyLIFtRqre9Mh9kO7Qt9rQvpfVeB7w,49217
19
19
  sqlglot/transforms.py,sha256=utNDsCBsA7hPUK3-aby3DDgiY_XVMAKQqeoLm1EyihI,41218
20
20
  sqlglot/trie.py,sha256=v27uXMrHfqrXlJ6GmeTSMovsB_3o0ctnlKhdNt7W6fI,2245
21
- sqlglot/dialects/__init__.py,sha256=e3K2NHrZO7oXfBzEpRsvgWAgJ_UCEyg7SlUCRqvnPj4,3799
21
+ sqlglot/dialects/__init__.py,sha256=g3HRtyb32r3LooiHKTzuUNB0_rBO_RauuOegp42gB48,3811
22
22
  sqlglot/dialects/athena.py,sha256=ofArmayYLev4qZQ15GM8mevG04qqR5WGFb2ZcuYm6x4,10966
23
23
  sqlglot/dialects/bigquery.py,sha256=9Q-oCXcpa2vrT2eMgVGHWwEvECMm4RQeV1XkjJj0nPA,72483
24
- sqlglot/dialects/clickhouse.py,sha256=OzcDAS7pWs889gu0aLYn_HIjTLiPRjGFG61p6-84stE,58574
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=BU4-x1d8tRCSAfdpeP5b86me0wXfgpcEPdyk27wLgg4,73293
26
+ sqlglot/dialects/dialect.py,sha256=KfBctpr7VdrCdHrP1Tk7CqAml53tRq9x-aDAkaN-9l0,73540
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=GylLuN7ANQmC-1o-8k51T4nIP4r5LbLOpZ-JBr13j0E,54328
31
+ sqlglot/dialects/duckdb.py,sha256=hLbLqkh5X5Nx3y5yfbBc5h9ye6UWTiZr_VsS4BMY5Rw,54612
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
35
  sqlglot/dialects/hive.py,sha256=UGIkXjMCk5a9ndUXQtvfG560oi3emdpqOYLQCmGabBk,32046
36
36
  sqlglot/dialects/materialize.py,sha256=LD2q1kTRrCwkIu1BfoBvnjTGbupDtoQ8JQMDCIYAXHg,3533
37
- sqlglot/dialects/mysql.py,sha256=0s7RIpWPCaBYvxgR8Z6JWPub1BHBB_0Th7KHCIXVfms,49702
37
+ sqlglot/dialects/mysql.py,sha256=xxVAR-pXMljYCUioavP3nROtOqKmK4kfdp4WWXX7X9g,50049
38
38
  sqlglot/dialects/oracle.py,sha256=zWPCpzGiTlgCJ5E6FjfX3Rszjcw4SnHg6xeVboMYIyo,15972
39
- sqlglot/dialects/postgres.py,sha256=sjqHOabifDKaG-OZB_ZxhShPcWrGOI4yRWZliVAfUaE,34802
39
+ sqlglot/dialects/postgres.py,sha256=_pXSu29684utgeuzPziSJ0Sw54WEIIunwLugJw7KFD8,34853
40
40
  sqlglot/dialects/presto.py,sha256=XVeYr2NP86x5enlRqI7MYR6le85_ucYg_BBRocGN3jM,33413
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
43
  sqlglot/dialects/risingwave.py,sha256=BqWwW1iT_OIVMwfRamaww79snnBwIgCfr22Go-ggO68,3289
44
44
  sqlglot/dialects/singlestore.py,sha256=0QqNYOucNklPQuyeGcsisLI97qPGx_RfWKOFarJz2qw,61711
45
- sqlglot/dialects/snowflake.py,sha256=VcQxRiy7KV7EtOp4IXe9Vx7qEjqf4ltpRKV2xAYNZbs,76311
45
+ sqlglot/dialects/snowflake.py,sha256=RqCEW6nMXlkJ-XqS3W4HA-ZPSQTHv_bc_CMBTmvZJOk,78425
46
+ sqlglot/dialects/solr.py,sha256=pydnl4ml-3M1Fc4ALm6cMVO9h-5EtqZxPZH_91Nz1Ss,617
46
47
  sqlglot/dialects/spark.py,sha256=PzyhkelDzbCMgJ3RVHD6yyzLIFp9NdZfwVas5IymowM,10147
47
48
  sqlglot/dialects/spark2.py,sha256=qz36FT9k4iuiqboRpyG4VpKGkPR0P2fifmqgZ9gNUEU,14851
48
49
  sqlglot/dialects/sqlite.py,sha256=zzXEbnaLjJeg6hPLHricjpfSkuf8tpXECnjcHtoqIbw,13263
@@ -74,10 +75,10 @@ sqlglot/optimizer/qualify.py,sha256=oAPfwub7dEkrlCrsptcJWpLya4BgKhN6M5SwIs_86LY,
74
75
  sqlglot/optimizer/qualify_columns.py,sha256=7aabZhD-dKNiwIW_ZjOEr0RPbWfhSbuR-WI6NnVCZAA,45298
75
76
  sqlglot/optimizer/qualify_tables.py,sha256=dA4ZazL7ShQh2JgBwpHuG-4c5lBw1TNzCnuN7m0iVTA,6645
76
77
  sqlglot/optimizer/scope.py,sha256=UOTrbwqcTc5iRQf0WStgYWXpE24w6riZy-tJYA18yTw,31229
77
- sqlglot/optimizer/simplify.py,sha256=-_yus42OYwqjQ9a2TSGhtG2G0pSkInUry1z7hEMz2pY,51062
78
+ sqlglot/optimizer/simplify.py,sha256=27IYsqbz1kyMlURSfRkm_ADSQJg-4805AOMFOjKKytU,51049
78
79
  sqlglot/optimizer/unnest_subqueries.py,sha256=kzWUVDlxs8z9nmRx-8U-pHXPtVZhEIwkKqmKhr2QLvc,10908
79
- sqlglot-27.16.3.dist-info/licenses/LICENSE,sha256=p1Yk0B4oa0l8Rh-_dYyy75d8spjPd_vTloXfz4FWxys,1065
80
- sqlglot-27.16.3.dist-info/METADATA,sha256=6KC1cCyCXpopLrMeA2rn-Z595AJ3fV_OaMH-i0Mg_G0,20682
81
- sqlglot-27.16.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
- sqlglot-27.16.3.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
83
- sqlglot-27.16.3.dist-info/RECORD,,
80
+ sqlglot-27.18.0.dist-info/licenses/LICENSE,sha256=p1Yk0B4oa0l8Rh-_dYyy75d8spjPd_vTloXfz4FWxys,1065
81
+ sqlglot-27.18.0.dist-info/METADATA,sha256=UxjiFljC-bu1PB4zuYgt_3aWXJu36fhzoq05oQnH8jU,20703
82
+ sqlglot-27.18.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
+ sqlglot-27.18.0.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
84
+ sqlglot-27.18.0.dist-info/RECORD,,