sqlglotc 30.20.0__tar.gz → 30.22.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {sqlglotc-30.20.0/sqlglotc.egg-info → sqlglotc-30.22.0}/PKG-INFO +3 -3
  2. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/pyproject.toml +2 -2
  3. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/setup.py +0 -1
  4. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/array.py +15 -1
  5. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/builders.py +1 -1
  6. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/ddl.py +1 -1
  7. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/properties.py +3 -5
  8. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/query.py +41 -3
  9. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/string.py +4 -0
  10. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generator.py +150 -34
  11. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/athena.py +1 -1
  12. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/bigquery.py +4 -2
  13. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/clickhouse.py +21 -0
  14. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/doris.py +1 -0
  15. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/dremio.py +1 -0
  16. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/drill.py +7 -0
  17. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/duckdb.py +76 -9
  18. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/hive.py +1 -0
  19. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/materialize.py +6 -1
  20. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/mysql.py +25 -1
  21. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/oracle.py +1 -0
  22. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/postgres.py +15 -1
  23. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/presto.py +9 -0
  24. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/redshift.py +8 -0
  25. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/risingwave.py +2 -1
  26. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/singlestore.py +11 -2
  27. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/snowflake.py +5 -48
  28. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/spark.py +3 -3
  29. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/spark2.py +10 -0
  30. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/sqlite.py +2 -1
  31. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/starrocks.py +8 -0
  32. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/trino.py +1 -0
  33. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/tsql.py +12 -4
  34. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/lineage.py +19 -6
  35. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/canonicalize_internal_names.py +6 -4
  36. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/qualify.py +0 -3
  37. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/qualify_columns.py +132 -40
  38. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/qualify_tables.py +32 -8
  39. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/resolver.py +46 -38
  40. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/scope.py +31 -13
  41. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/simplify.py +10 -3
  42. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parser.py +251 -79
  43. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/athena.py +5 -2
  44. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/clickhouse.py +14 -0
  45. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/doris.py +3 -0
  46. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/duckdb.py +3 -2
  47. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/hive.py +37 -1
  48. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/mysql.py +30 -5
  49. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/postgres.py +3 -0
  50. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/snowflake.py +11 -0
  51. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/spark.py +6 -0
  52. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/spark2.py +8 -1
  53. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/starrocks.py +3 -0
  54. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/trino.py +25 -0
  55. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/tsql.py +72 -2
  56. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/tokenizer_core.py +207 -22
  57. {sqlglotc-30.20.0 → sqlglotc-30.22.0/sqlglotc.egg-info}/PKG-INFO +3 -3
  58. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglotc.egg-info/SOURCES.txt +0 -1
  59. sqlglotc-30.22.0/sqlglotc.egg-info/requires.txt +6 -0
  60. sqlglotc-30.22.0/sqlglotc.egg-info/scm_version.json +8 -0
  61. sqlglotc-30.20.0/sqlglot/schema.py +0 -813
  62. sqlglotc-30.20.0/sqlglotc.egg-info/requires.txt +0 -6
  63. sqlglotc-30.20.0/sqlglotc.egg-info/scm_version.json +0 -8
  64. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/MANIFEST.in +0 -0
  65. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/setup.cfg +0 -0
  66. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/anonymize.py +0 -0
  67. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/errors.py +0 -0
  68. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/executor/table.py +0 -0
  69. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/aggregate.py +0 -0
  70. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/constraints.py +0 -0
  71. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/core.py +0 -0
  72. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/datatypes.py +0 -0
  73. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/dml.py +0 -0
  74. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/functions.py +0 -0
  75. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/json.py +0 -0
  76. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/math.py +0 -0
  77. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/expressions/temporal.py +0 -0
  78. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/databricks.py +0 -0
  79. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/dax.py +0 -0
  80. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/druid.py +0 -0
  81. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/dune.py +0 -0
  82. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/exasol.py +0 -0
  83. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/fabric.py +0 -0
  84. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/prql.py +0 -0
  85. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/python.py +0 -0
  86. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/solr.py +0 -0
  87. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/tableau.py +0 -0
  88. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/generators/teradata.py +0 -0
  89. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/helper.py +0 -0
  90. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/annotate_types.py +0 -0
  91. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  92. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  93. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/base.py +0 -0
  94. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/bigquery.py +0 -0
  95. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/databricks.py +0 -0
  96. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/dax.py +0 -0
  97. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/dremio.py +0 -0
  98. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/drill.py +0 -0
  99. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/druid.py +0 -0
  100. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/dune.py +0 -0
  101. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/exasol.py +0 -0
  102. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/fabric.py +0 -0
  103. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/materialize.py +0 -0
  104. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/oracle.py +0 -0
  105. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/presto.py +0 -0
  106. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/prql.py +0 -0
  107. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/redshift.py +0 -0
  108. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/risingwave.py +0 -0
  109. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/singlestore.py +0 -0
  110. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/solr.py +0 -0
  111. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/sqlite.py +0 -0
  112. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/tableau.py +0 -0
  113. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/parsers/teradata.py +0 -0
  114. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/serde.py +0 -0
  115. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/time.py +0 -0
  116. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglot/trie.py +0 -0
  117. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  118. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  119. {sqlglotc-30.20.0 → sqlglotc-30.22.0}/sqlglotc.egg-info/top_level.txt +0 -0
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.20.0
3
+ Version: 30.22.0
4
4
  Summary: mypyc-compiled extensions for sqlglot
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://sqlglot.com/
8
8
  Project-URL: Repository, https://github.com/tobymao/sqlglot
9
9
  Requires-Python: >=3.10
10
- Requires-Dist: sqlglot==30.20.0
10
+ Requires-Dist: sqlglot==30.22.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
14
- Requires-Dist: sqlglot-mypy>=2.3.0.post2; extra == "dev"
14
+ Requires-Dist: mypy>=2.4.0; extra == "dev"
15
15
  Dynamic: requires-dist
@@ -7,7 +7,7 @@ license = "MIT"
7
7
  requires-python = ">= 3.10"
8
8
 
9
9
  [project.optional-dependencies]
10
- dev = ["setuptools >= 61.0", "setuptools_scm", "sqlglot-mypy >= 2.3.0.post2"]
10
+ dev = ["setuptools >= 61.0", "setuptools_scm", "mypy >= 2.4.0"]
11
11
 
12
12
  [project.urls]
13
13
  Homepage = "https://sqlglot.com/"
@@ -17,7 +17,7 @@ Repository = "https://github.com/tobymao/sqlglot"
17
17
  requires = [
18
18
  "setuptools >= 61.0",
19
19
  "setuptools_scm",
20
- "sqlglot-mypy >= 2.3.0.post2",
20
+ "mypy >= 2.4.0",
21
21
  "types-python-dateutil",
22
22
  "sqlglot",
23
23
  ]
@@ -49,7 +49,6 @@ def _source_files(src_dir):
49
49
  "helper.py",
50
50
  "lineage.py",
51
51
  "parser.py",
52
- "schema.py",
53
52
  "serde.py",
54
53
  "time.py",
55
54
  "tokenizer_core.py",
@@ -23,6 +23,7 @@ class Array(Expression, Func):
23
23
  "expressions": False,
24
24
  "bracket_notation": False,
25
25
  "struct_name_inheritance": False,
26
+ "value_constructor": False,
26
27
  }
27
28
  is_var_len_args = True
28
29
 
@@ -84,7 +85,16 @@ class ArrayReverse(Expression, Func):
84
85
 
85
86
 
86
87
  class ArraySlice(Expression, Func):
87
- arg_types = {"this": True, "start": True, "end": False, "step": False, "zero_based": False}
88
+ """Slice by end index, or by length when is_length is set."""
89
+
90
+ arg_types = {
91
+ "this": True,
92
+ "start": True,
93
+ "end": False,
94
+ "step": False,
95
+ "zero_based": False,
96
+ "is_length": False,
97
+ }
88
98
 
89
99
 
90
100
  class ArraySort(Expression, Func):
@@ -244,6 +254,10 @@ class Inline(Expression, Func, UDTF):
244
254
  pass
245
255
 
246
256
 
257
+ class SplitToTable(Expression, Func, UDTF):
258
+ arg_types = {"this": True, "expression": True}
259
+
260
+
247
261
  class Stack(Expression, Func, UDTF):
248
262
  arg_types = {"this": True, "expressions": True}
249
263
  is_var_len_args = True
@@ -516,7 +516,7 @@ def cast(
516
516
 
517
517
 
518
518
  def table_(
519
- table: Identifier | str,
519
+ table: Identifier | str | None,
520
520
  db: Identifier | str | None = None,
521
521
  catalog: Identifier | str | None = None,
522
522
  quoted: bool | None = None,
@@ -400,7 +400,7 @@ class Alter(Expression):
400
400
  arg_types = {
401
401
  "this": False,
402
402
  "kind": True,
403
- "actions": True,
403
+ "actions": False,
404
404
  "exists": False,
405
405
  "only": False,
406
406
  "options": False,
@@ -624,7 +624,9 @@ class Properties(Expression):
624
624
  "INCLUDE": IncludeProperty,
625
625
  }
626
626
 
627
- PROPERTY_TO_NAME: t.ClassVar[dict[type[Property], str]] = {}
627
+ PROPERTY_TO_NAME: t.ClassVar[dict[type[Property], str]] = {
628
+ v: k for k, v in NAME_TO_PROPERTY.items()
629
+ }
628
630
 
629
631
  # CREATE property locations
630
632
  # Form: schema specified
@@ -652,7 +654,3 @@ class Properties(Expression):
652
654
  expressions.append(Property(this=Literal.string(key), value=convert(value)))
653
655
 
654
656
  return cls(expressions=expressions)
655
-
656
-
657
- # TODO (mypyc)
658
- Properties.PROPERTY_TO_NAME = {v: k for k, v in Properties.NAME_TO_PROPERTY.items()}
@@ -19,6 +19,7 @@ from sqlglot.expressions.core import (
19
19
  Hint,
20
20
  Identifier,
21
21
  In,
22
+ Var,
22
23
  _apply_builder,
23
24
  _apply_child_list_builder,
24
25
  _apply_list_builder,
@@ -441,11 +442,24 @@ class QueryBand(Expression):
441
442
 
442
443
 
443
444
  class RecursiveWithSearch(Expression):
444
- arg_types = {"kind": True, "this": True, "expression": True, "using": False}
445
+ arg_types = {
446
+ "kind": True,
447
+ "this": True,
448
+ "expression": True,
449
+ "to": False,
450
+ "default": False,
451
+ "using": False,
452
+ }
445
453
 
446
454
 
447
455
  class With(Expression):
448
- arg_types = {"expressions": False, "recursive": False, "search": False, "udfs": False}
456
+ arg_types = {
457
+ "expressions": False,
458
+ "recursive": False,
459
+ "search": False,
460
+ "cycle": False,
461
+ "udfs": False,
462
+ }
449
463
 
450
464
  @property
451
465
  def recursive(self) -> bool:
@@ -572,7 +586,7 @@ class ConditionalInsert(Expression):
572
586
 
573
587
 
574
588
  class MultitableInserts(Expression):
575
- arg_types = {"expressions": True, "kind": True, "source": True}
589
+ arg_types = {"with_": False, "expressions": True, "kind": False, "source": True}
576
590
 
577
591
 
578
592
  class OnCondition(Expression):
@@ -657,6 +671,11 @@ class Limit(Expression):
657
671
  "expressions": False,
658
672
  }
659
673
 
674
+ @property
675
+ def is_limit_all(self) -> bool:
676
+ limit_expression = self.args.get("expression")
677
+ return isinstance(limit_expression, Var) and limit_expression.name.upper() == "ALL"
678
+
660
679
 
661
680
  class LimitOptions(Expression):
662
681
  arg_types = {
@@ -804,6 +823,19 @@ class Lateral(Expression, UDTF):
804
823
  "ordinality": False,
805
824
  }
806
825
 
826
+ @property
827
+ def selects(self) -> list[Expr]:
828
+ from sqlglot.expressions.array import Unnest
829
+
830
+ columns = super().selects
831
+
832
+ # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
833
+ offset = self.this.args.get("offset") if isinstance(self.this, Unnest) else None
834
+ if isinstance(offset, Identifier):
835
+ columns = columns + [offset]
836
+
837
+ return columns
838
+
807
839
 
808
840
  class TableFromRows(Expression, UDTF):
809
841
  arg_types = {
@@ -941,6 +973,12 @@ class Get(Expression):
941
973
  arg_types = {"this": True, "target": True, "properties": False}
942
974
 
943
975
 
976
+ class ListStage(Expression):
977
+ """Lists files in a Snowflake stage or Git repository clone."""
978
+
979
+ arg_types = {"this": True, "pattern": False}
980
+
981
+
944
982
  class Table(Expression, Selectable):
945
983
  arg_types = {
946
984
  "this": False,
@@ -260,6 +260,10 @@ class Upper(Expression, Func):
260
260
  _sql_names = ["UPPER", "UCASE"]
261
261
 
262
262
 
263
+ class WeightString(Expression, Func):
264
+ arg_types = {"this": True, "to": False}
265
+
266
+
263
267
  # Encoding / base conversion
264
268
 
265
269
 
@@ -25,6 +25,8 @@ if t.TYPE_CHECKING:
25
25
 
26
26
  logger = logging.getLogger("sqlglot")
27
27
 
28
+ _HIGH_BYTE_RE = re.compile(r"[\x80-\xff]")
29
+
28
30
  ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
29
31
  UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
30
32
 
@@ -380,6 +382,12 @@ class Generator:
380
382
  # Whether comparing against booleans (e.g. x IS TRUE) is supported
381
383
  IS_BOOL_ALLOWED = True
382
384
 
385
+ # Whether casting a fractional number to an integer rounds it instead of truncating it
386
+ CAST_TO_INT_ROUNDS = False
387
+
388
+ # Whether the native integer division operator returns NULL for a zero divisor instead of raising
389
+ SAFE_INT_DIVISION = False
390
+
383
391
  # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
384
392
  DUPLICATE_KEY_UPDATE_WITH_SET = True
385
393
 
@@ -565,6 +573,9 @@ class Generator:
565
573
  # The HEX function name
566
574
  HEX_FUNC = "HEX"
567
575
 
576
+ # Function that constructs a binary value from a hex-encoded byte string.
577
+ BYTE_STRING_FUNCTION = ""
578
+
568
579
  # The keywords to use when prefixing & separating WITH based properties
569
580
  WITH_PROPERTIES_PREFIX = "WITH"
570
581
 
@@ -871,6 +882,10 @@ class Generator:
871
882
  exp.Mod,
872
883
  )
873
884
 
885
+ # ALTER options that follow the others without a comma, e.g. MySQL's
886
+ # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
887
+ ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
888
+
874
889
  SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
875
890
 
876
891
  SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
@@ -892,6 +907,7 @@ class Generator:
892
907
  "_escaped_quote_end",
893
908
  "_escaped_byte_quote_end",
894
909
  "_escaped_identifier_end",
910
+ "_identifier_escaped_sequences",
895
911
  "_next_name",
896
912
  "_identifier_start",
897
913
  "_identifier_end",
@@ -944,6 +960,7 @@ class Generator:
944
960
  else ""
945
961
  )
946
962
  self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
963
+ self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
947
964
 
948
965
  self._next_name = name_sequence("_t")
949
966
 
@@ -1569,8 +1586,10 @@ class Generator:
1569
1586
  )
1570
1587
  search = self.sql(expression, "search")
1571
1588
  search = f" {search}" if search else ""
1589
+ cycle = self.sql(expression, "cycle")
1590
+ cycle = f" {cycle}" if cycle else ""
1572
1591
 
1573
- sql = f"WITH {recursive}{sql}{search}" if sql else ""
1592
+ sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1574
1593
  return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1575
1594
 
1576
1595
  def cte_sql(self, expression: exp.CTE) -> str:
@@ -1642,15 +1661,23 @@ class Generator:
1642
1661
 
1643
1662
  def bytestring_sql(self, expression: exp.ByteString) -> str:
1644
1663
  this = self.sql(expression, "this")
1664
+ is_bytes = bool(expression.args.get("is_bytes"))
1665
+ if is_bytes and self.BYTE_STRING_FUNCTION:
1666
+ # Hex digits can be quoted directly without escaping.
1667
+ return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1668
+
1645
1669
  if self.dialect.BYTE_START:
1646
1670
  escaped_byte_string = self.escape_str(
1647
1671
  this,
1648
- escape_backslash=False,
1672
+ escape_backslash=bool(
1673
+ self.dialect.tokenizer_class.NUMERIC_ESCAPES
1674
+ or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1675
+ ),
1649
1676
  delimiter=self.dialect.BYTE_END,
1650
1677
  escaped_delimiter=self._escaped_byte_quote_end,
1651
1678
  is_byte_string=True,
1679
+ is_bytes=is_bytes,
1652
1680
  )
1653
- is_bytes = expression.args.get("is_bytes", False)
1654
1681
  delimited_byte_string = (
1655
1682
  f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1656
1683
  )
@@ -1787,7 +1814,8 @@ class Generator:
1787
1814
  if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1788
1815
  type_sql = self.sql(expression, "kind")
1789
1816
  elif type_value == exp.DType.CHARACTER_SET:
1790
- return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1817
+ char = f"CHAR({interior})" if interior else "CHAR"
1818
+ return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1791
1819
  else:
1792
1820
  type_sql = (
1793
1821
  self.TYPE_MAPPING.get(type_value, type_value.value)
@@ -2031,6 +2059,9 @@ class Generator:
2031
2059
  lower = text.lower()
2032
2060
  quoted = expression.quoted
2033
2061
  text = lower if self.normalize and not quoted else text
2062
+ if self._identifier_escaped_sequences:
2063
+ for char, escaped in self._identifier_escaped_sequences.items():
2064
+ text = text.replace(char, escaped)
2034
2065
  text = text.replace(self._identifier_end, self._escaped_identifier_end)
2035
2066
  if (
2036
2067
  quoted
@@ -2959,7 +2990,20 @@ class Generator:
2959
2990
  op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2960
2991
  return f"{op_sql}{self.sep()}{this}{table}{columns}"
2961
2992
 
2962
- alias = self.sql(expression, "alias")
2993
+ table_alias = expression.args.get("alias")
2994
+ offset = expression.this.args.get("offset")
2995
+
2996
+ if (
2997
+ self.UNNEST_WITH_ORDINALITY
2998
+ and table_alias
2999
+ and isinstance(expression.this, exp.Unnest)
3000
+ and isinstance(offset, exp.Identifier)
3001
+ ):
3002
+ # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3003
+ table_alias = table_alias.copy()
3004
+ table_alias.append("columns", offset.copy())
3005
+
3006
+ alias = self.sql(table_alias)
2963
3007
  alias = f" AS {alias}" if alias else ""
2964
3008
 
2965
3009
  ordinality = expression.args.get("ordinality") or ""
@@ -2972,6 +3016,9 @@ class Generator:
2972
3016
  def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2973
3017
  this = self.sql(expression, "this")
2974
3018
 
3019
+ if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3020
+ return this
3021
+
2975
3022
  args = [
2976
3023
  self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2977
3024
  for e in (expression.args.get(k) for k in ("offset", "expression"))
@@ -3059,6 +3106,7 @@ class Generator:
3059
3106
  delimiter: str | None = None,
3060
3107
  escaped_delimiter: str | None = None,
3061
3108
  is_byte_string: bool = False,
3109
+ is_bytes: bool = False,
3062
3110
  ) -> str:
3063
3111
  if is_byte_string:
3064
3112
  supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
@@ -3071,6 +3119,10 @@ class Generator:
3071
3119
  for ch in text
3072
3120
  )
3073
3121
 
3122
+ if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3123
+ # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3124
+ text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3125
+
3074
3126
  delimiter = delimiter or self.dialect.QUOTE_END
3075
3127
  escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3076
3128
 
@@ -3171,7 +3223,9 @@ class Generator:
3171
3223
  if not (isinstance(this, exp.Column) and not this.table):
3172
3224
  return None
3173
3225
 
3174
- ancestor = expression.find_ancestor(exp.Select, exp.Window)
3226
+ # Only a query's own ORDER BY can reference its projection aliases
3227
+ order = expression.parent
3228
+ ancestor = order.parent if isinstance(order, exp.Order) else None
3175
3229
  if not isinstance(ancestor, exp.Select):
3176
3230
  return None
3177
3231
 
@@ -3321,7 +3375,9 @@ class Generator:
3321
3375
  limit = exp.Limit(
3322
3376
  expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3323
3377
  )
3324
- elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3378
+ elif (
3379
+ self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3380
+ ):
3325
3381
  limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3326
3382
 
3327
3383
  return csv(
@@ -4371,10 +4427,12 @@ class Generator:
4371
4427
  return f"SET {exprs}"
4372
4428
 
4373
4429
  def alter_sql(self, expression: exp.Alter) -> str:
4374
- actions = expression.args["actions"]
4430
+ actions = expression.actions
4375
4431
 
4376
- if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4377
- actions[0], exp.ColumnDef
4432
+ if (
4433
+ not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4434
+ and actions
4435
+ and isinstance(actions[0], exp.ColumnDef)
4378
4436
  ):
4379
4437
  actions_sql = self.expressions(expression, key="actions", flat=True)
4380
4438
  actions_sql = f"ADD {actions_sql}"
@@ -4401,8 +4459,18 @@ class Generator:
4401
4459
  on_cluster = self.sql(expression, "cluster")
4402
4460
  on_cluster = f" {on_cluster}" if on_cluster else ""
4403
4461
  only = " ONLY" if expression.args.get("only") else ""
4404
- options = self.expressions(expression, key="options")
4405
- options = f", {options}" if options else ""
4462
+ options = expression.args.get("options") or []
4463
+ options_sql = self.expressions(
4464
+ sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4465
+ )
4466
+ options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4467
+ partitions_sql = self.expressions(
4468
+ sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4469
+ sep=" ",
4470
+ flat=True,
4471
+ )
4472
+ if partitions_sql and (actions_sql or options_sql):
4473
+ partitions_sql = f" {partitions_sql}"
4406
4474
  kind = self.sql(expression, "kind")
4407
4475
  not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4408
4476
  check = " WITH CHECK" if expression.args.get("check") else ""
@@ -4414,7 +4482,7 @@ class Generator:
4414
4482
  this = self.sql(expression, "this")
4415
4483
  this = f" {this}" if this else ""
4416
4484
 
4417
- return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4485
+ return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4418
4486
 
4419
4487
  def altersession_sql(self, expression: exp.AlterSession) -> str:
4420
4488
  items_sql = self.expressions(expression, flat=True)
@@ -4477,12 +4545,14 @@ class Generator:
4477
4545
  return f"{this_sql} HAVING {kind} {expression_sql}"
4478
4546
 
4479
4547
  def intdiv_sql(self, expression: exp.IntDiv) -> str:
4480
- return self.sql(
4481
- exp.Cast(
4482
- this=exp.Div(this=expression.this, expression=expression.expression),
4483
- to=exp.DataType(this=exp.DType.INT),
4484
- )
4485
- )
4548
+ return self._truncated_div_sql(expression.this, expression.expression)
4549
+
4550
+ def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4551
+ div: exp.Expr = exp.Div(this=this, expression=divisor)
4552
+ if self.CAST_TO_INT_ROUNDS:
4553
+ div = exp.Trunc(this=div)
4554
+
4555
+ return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4486
4556
 
4487
4557
  def dpipe_sql(self, expression: exp.DPipe) -> str:
4488
4558
  if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
@@ -4501,12 +4571,14 @@ class Generator:
4501
4571
 
4502
4572
  elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4503
4573
  if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4504
- return self.sql(
4505
- exp.cast(
4506
- l / r,
4507
- to=exp.DType.BIGINT,
4508
- )
4509
- )
4574
+ # Native integer division depends on the target's operand types, which may differ
4575
+ # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4576
+ # integer literals or casts
4577
+ if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4578
+ expression.args.get("safe") or not self.SAFE_INT_DIVISION
4579
+ ):
4580
+ return self.sql(exp.IntDiv(this=l, expression=r))
4581
+ return self._truncated_div_sql(l, r)
4510
4582
 
4511
4583
  return self.binary(expression, "/")
4512
4584
 
@@ -4727,12 +4799,11 @@ class Generator:
4727
4799
  elif arg_value is not None:
4728
4800
  args.append(arg_value)
4729
4801
 
4730
- if self.dialect.PRESERVE_ORIGINAL_NAMES:
4731
- name = expression.meta_get("name") or expression.sql_name()
4732
- else:
4733
- name = expression.sql_name()
4802
+ return self.func(self.function_name(expression), *args)
4734
4803
 
4735
- return self.func(name, *args)
4804
+ def function_name(self, expression: exp.Func) -> str:
4805
+ name_key = self.dialect.ORIGINAL_NAME_META_KEY
4806
+ return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4736
4807
 
4737
4808
  def func(
4738
4809
  self,
@@ -5309,6 +5380,28 @@ class Generator:
5309
5380
  sqlglot.dialects.dialect.unit_to_str(expression),
5310
5381
  )
5311
5382
 
5383
+ def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5384
+ this = expression.this
5385
+ position = expression.args["position"]
5386
+ offset = index_offset - (expression.args.get("offset") or 0)
5387
+
5388
+ if offset:
5389
+ if position.is_int:
5390
+ value = position.to_py()
5391
+ if value >= 0:
5392
+ position = exp.Literal.number(value + offset)
5393
+ elif offset < 0 and value == -1:
5394
+ # 1-based -1 appends, which a 0-based position can only express as the size
5395
+ position = exp.ArraySize(this=this.copy())
5396
+ else:
5397
+ # Negative positions count from the end, so they shift in the opposite
5398
+ # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5399
+ position = exp.Literal.number(value - offset)
5400
+ else:
5401
+ self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5402
+
5403
+ return self.func("ARRAY_INSERT", this, position, expression.expression)
5404
+
5312
5405
  def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5313
5406
  if self.CAN_IMPLEMENT_ARRAY_ANY:
5314
5407
  filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
@@ -5710,9 +5803,18 @@ class Generator:
5710
5803
 
5711
5804
  def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5712
5805
  kind = self.sql(expression, "kind")
5713
- expressions = self.seg(self.expressions(expression, sep=" "))
5714
- res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5715
- return res
5806
+ source = self.sql(expression, "source")
5807
+
5808
+ if kind:
5809
+ expressions = self.seg(self.expressions(expression, sep=" "))
5810
+ sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5811
+ else:
5812
+ # If kind isn't present, we have Hive's FROM-first form:
5813
+ # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5814
+ inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5815
+ sql = f"FROM {source}{self.seg(inserts)}"
5816
+
5817
+ return self.prepend_ctes(expression, sql)
5716
5818
 
5717
5819
  def oncondition_sql(self, expression: exp.OnCondition) -> str:
5718
5820
  # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
@@ -6148,12 +6250,16 @@ class Generator:
6148
6250
  kind = self.sql(expression, "kind")
6149
6251
  this = self.sql(expression, "this")
6150
6252
  set = self.sql(expression, "expression")
6253
+ to = self.sql(expression, "to")
6254
+ to = f" TO {to}" if to else ""
6255
+ default = self.sql(expression, "default")
6256
+ default = f" DEFAULT {default}" if default else ""
6151
6257
  using = self.sql(expression, "using")
6152
6258
  using = f" USING {using}" if using else ""
6153
6259
 
6154
6260
  kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6155
6261
 
6156
- return f"{kind_sql} {this} SET {set}{using}"
6262
+ return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6157
6263
 
6158
6264
  def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6159
6265
  params = self.expressions(expression, key="params", flat=True)
@@ -6176,6 +6282,11 @@ class Generator:
6176
6282
  self.unsupported("Unsupported INSTALL statement")
6177
6283
  return ""
6178
6284
 
6285
+ def liststage_sql(self, expression: exp.ListStage) -> str:
6286
+ pattern = self.sql(expression, "pattern")
6287
+ pattern = f" PATTERN = {pattern}" if pattern else ""
6288
+ return f"LIST {self.sql(expression, 'this')}{pattern}"
6289
+
6179
6290
  def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6180
6291
  # Snowflake GET/PUT statements:
6181
6292
  # PUT <file> <internalStage> <properties>
@@ -6354,6 +6465,11 @@ class Generator:
6354
6465
  using = f" USING {charset}" if charset else ""
6355
6466
  return self.func(name, this + using)
6356
6467
 
6468
+ def weightstring_sql(self, expression: exp.WeightString) -> str:
6469
+ to = self.sql(expression, "to")
6470
+ to = f" AS {to}" if to else ""
6471
+ return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6472
+
6357
6473
  def block_sql(self, expression: exp.Block) -> str:
6358
6474
  expressions = self.expressions(expression, sep="; ", flat=True)
6359
6475
  begin = "BEGIN " if expression.args.get("begin") else ""
@@ -122,7 +122,7 @@ class AthenaTrinoGenerator(TrinoGenerator):
122
122
  }
123
123
 
124
124
 
125
- class AthenaGenerator(generator.Generator):
125
+ class AthenaGenerator(AthenaTrinoGenerator):
126
126
  SELECT_KINDS: tuple[str, ...] = ()
127
127
  SUPPORTS_DECODE_CASE = False
128
128
 
@@ -15,6 +15,7 @@ from sqlglot.dialects.dialect import (
15
15
  inline_array_unless_query,
16
16
  max_or_greatest,
17
17
  min_or_least,
18
+ no_filter_sql,
18
19
  no_ilike_sql,
19
20
  regexp_replace_sql,
20
21
  rename_func,
@@ -229,8 +230,7 @@ def _levenshtein_sql(self: BigQueryGenerator, expression: exp.Levenshtein) -> st
229
230
 
230
231
 
231
232
  def _json_extract_sql(self: BigQueryGenerator, expression: JSON_EXTRACT_TYPE) -> str:
232
- name = expression.meta_get("name") or expression.sql_name()
233
- upper = name.upper()
233
+ upper = self.function_name(expression).upper()
234
234
 
235
235
  dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS
236
236
 
@@ -257,6 +257,7 @@ class BigQueryGenerator(generator.Generator):
257
257
  RENAME_TABLE_WITH_DB = False
258
258
  NVL2_SUPPORTED = False
259
259
  UNNEST_WITH_ORDINALITY = False
260
+ CAST_TO_INT_ROUNDS = True
260
261
  COLLATE_IS_FUNC = True
261
262
  LIMIT_ONLY_LITERALS = True
262
263
  SUPPORTS_TABLE_ALIAS_COLUMNS = False
@@ -335,6 +336,7 @@ class BigQueryGenerator(generator.Generator):
335
336
  exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
336
337
  exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
337
338
  exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"),
339
+ exp.Filter: no_filter_sql,
338
340
  exp.FromTimeZone: lambda self, e: self.func(
339
341
  "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
340
342
  ),