sqlglotc 30.11.0__tar.gz → 30.12.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 (116) hide show
  1. {sqlglotc-30.11.0/sqlglotc.egg-info → sqlglotc-30.12.0}/PKG-INFO +3 -3
  2. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/pyproject.toml +2 -2
  3. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/array.py +4 -0
  4. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/core.py +23 -10
  5. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/ddl.py +8 -0
  6. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/json.py +4 -0
  7. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/properties.py +5 -1
  8. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/query.py +2 -2
  9. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/temporal.py +5 -1
  10. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generator.py +64 -13
  11. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/clickhouse.py +4 -1
  12. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/databricks.py +1 -0
  13. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/duckdb.py +155 -6
  14. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/exasol.py +1 -3
  15. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/mysql.py +4 -0
  16. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/postgres.py +1 -1
  17. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/presto.py +2 -0
  18. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/snowflake.py +15 -1
  19. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/starrocks.py +15 -1
  20. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify_columns.py +21 -3
  21. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify_tables.py +7 -1
  22. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/resolver.py +4 -0
  23. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/simplify.py +57 -22
  24. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parser.py +63 -29
  25. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/bigquery.py +8 -3
  26. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/clickhouse.py +5 -0
  27. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/databricks.py +1 -0
  28. sqlglotc-30.12.0/sqlglot/parsers/druid.py +10 -0
  29. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/exasol.py +3 -2
  30. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/postgres.py +1 -0
  31. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/snowflake.py +75 -3
  32. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/starrocks.py +4 -1
  33. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/tokenizer_core.py +2 -0
  34. {sqlglotc-30.11.0 → sqlglotc-30.12.0/sqlglotc.egg-info}/PKG-INFO +3 -3
  35. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/SOURCES.txt +2 -0
  36. sqlglotc-30.12.0/sqlglotc.egg-info/requires.txt +6 -0
  37. sqlglotc-30.12.0/sqlglotc.egg-info/scm_file_list.json +7 -0
  38. sqlglotc-30.12.0/sqlglotc.egg-info/scm_version.json +8 -0
  39. sqlglotc-30.11.0/sqlglot/parsers/druid.py +0 -7
  40. sqlglotc-30.11.0/sqlglotc.egg-info/requires.txt +0 -6
  41. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/MANIFEST.in +0 -0
  42. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/setup.cfg +0 -0
  43. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/setup.py +0 -0
  44. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/errors.py +0 -0
  45. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/executor/table.py +0 -0
  46. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/aggregate.py +0 -0
  47. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/builders.py +0 -0
  48. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/constraints.py +0 -0
  49. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/datatypes.py +0 -0
  50. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/dml.py +0 -0
  51. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/functions.py +0 -0
  52. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/math.py +0 -0
  53. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/string.py +0 -0
  54. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/athena.py +0 -0
  55. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/bigquery.py +0 -0
  56. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/doris.py +0 -0
  57. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/dremio.py +0 -0
  58. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/drill.py +0 -0
  59. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/druid.py +0 -0
  60. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/dune.py +0 -0
  61. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/fabric.py +0 -0
  62. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/hive.py +0 -0
  63. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/materialize.py +0 -0
  64. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/oracle.py +0 -0
  65. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/prql.py +0 -0
  66. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/python.py +0 -0
  67. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/redshift.py +0 -0
  68. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/risingwave.py +0 -0
  69. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/singlestore.py +0 -0
  70. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/solr.py +0 -0
  71. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/spark.py +0 -0
  72. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/spark2.py +0 -0
  73. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/sqlite.py +0 -0
  74. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/tableau.py +0 -0
  75. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/teradata.py +0 -0
  76. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/trino.py +0 -0
  77. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/tsql.py +0 -0
  78. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/helper.py +0 -0
  79. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/lineage.py +0 -0
  80. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/annotate_types.py +0 -0
  81. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  82. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  83. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  84. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify.py +0 -0
  85. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/scope.py +0 -0
  86. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/athena.py +0 -0
  87. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/base.py +0 -0
  88. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/doris.py +0 -0
  89. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/dremio.py +0 -0
  90. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/drill.py +0 -0
  91. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/duckdb.py +0 -0
  92. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/dune.py +0 -0
  93. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/fabric.py +0 -0
  94. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/hive.py +0 -0
  95. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/materialize.py +0 -0
  96. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/mysql.py +0 -0
  97. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/oracle.py +0 -0
  98. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/presto.py +0 -0
  99. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/prql.py +0 -0
  100. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/redshift.py +0 -0
  101. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/risingwave.py +0 -0
  102. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/singlestore.py +0 -0
  103. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/solr.py +0 -0
  104. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/spark.py +0 -0
  105. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/spark2.py +0 -0
  106. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/sqlite.py +0 -0
  107. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/tableau.py +0 -0
  108. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/teradata.py +0 -0
  109. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/trino.py +0 -0
  110. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/tsql.py +0 -0
  111. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/schema.py +0 -0
  112. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/serde.py +0 -0
  113. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/time.py +0 -0
  114. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/trie.py +0 -0
  115. {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  116. {sqlglotc-30.11.0 → sqlglotc-30.12.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.11.0
3
+ Version: 30.12.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.11.0
10
+ Requires-Dist: sqlglot==30.12.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.1.0.post2; extra == "dev"
14
+ Requires-Dist: sqlglot-mypy>=2.1.0.post3; 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.1.0.post2"]
10
+ dev = ["setuptools >= 61.0", "setuptools_scm", "sqlglot-mypy >= 2.1.0.post3"]
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.1.0.post2",
20
+ "sqlglot-mypy >= 2.1.0.post3",
21
21
  "types-python-dateutil",
22
22
  "sqlglot",
23
23
  ]
@@ -114,6 +114,10 @@ class ArrayContainsAll(Expression, Binary, Func):
114
114
  _sql_names = ["ARRAY_CONTAINS_ALL", "ARRAY_HAS_ALL"]
115
115
 
116
116
 
117
+ class ArrayContainedBy(Expression, Binary, Func):
118
+ pass
119
+
120
+
117
121
  class ArrayExcept(Expression, Func):
118
122
  arg_types = {"this": True, "expression": True, "is_multiset": False}
119
123
 
@@ -10,12 +10,14 @@ import re
10
10
  import sys
11
11
  import textwrap
12
12
  import typing as t
13
+ from builtins import type as Type
13
14
  from collections import deque
15
+ from collections.abc import Collection, Iterator, Mapping, MutableMapping, Sequence
14
16
  from copy import deepcopy
15
17
  from decimal import Decimal
16
18
  from functools import reduce
17
- from collections.abc import Iterator, Sequence, Collection, Mapping, MutableMapping
18
- from sqlglot._typing import E, T
19
+
20
+ from sqlglot._typing import E, GeneratorNoDialectArgs, ParserNoDialectArgs, T
19
21
  from sqlglot.errors import ParseError
20
22
  from sqlglot.helper import (
21
23
  camel_to_snake_case,
@@ -24,17 +26,15 @@ from sqlglot.helper import (
24
26
  to_bool,
25
27
  trait,
26
28
  )
27
-
28
29
  from sqlglot.tokenizer_core import Token
29
- from builtins import type as Type
30
- from sqlglot._typing import GeneratorNoDialectArgs, ParserNoDialectArgs
31
30
 
32
31
  if t.TYPE_CHECKING:
33
- from typing_extensions import Self, Unpack, Concatenate
32
+ from typing_extensions import Concatenate, Self, Unpack
33
+
34
+ from sqlglot._typing import P
34
35
  from sqlglot.dialects.dialect import DialectType
35
36
  from sqlglot.expressions.datatypes import DATA_TYPE, DataType, DType, Interval
36
37
  from sqlglot.expressions.query import Select
37
- from sqlglot._typing import P
38
38
 
39
39
  R = t.TypeVar("R")
40
40
 
@@ -1789,7 +1789,12 @@ class JoinHint(Expression):
1789
1789
 
1790
1790
 
1791
1791
  class Identifier(Expression):
1792
- arg_types = {"this": True, "quoted": False, "global_": False, "temporary": False}
1792
+ arg_types = {
1793
+ "this": True,
1794
+ "quoted": False,
1795
+ "global_": False,
1796
+ "temporary": False,
1797
+ }
1793
1798
  is_primitive = True
1794
1799
  _hash_raw_args = True
1795
1800
 
@@ -1802,12 +1807,19 @@ class Identifier(Expression):
1802
1807
  return self.name
1803
1808
 
1804
1809
 
1810
+ # https://docs.snowflake.com/en/sql-reference/identifier-literal
1811
+ # "expressions" holds the arguments when the resolved identifier is invoked as a
1812
+ # function, e.g. `IDENTIFIER('my_func')(1, 2)`
1813
+ class DynamicIdentifier(Expression, Func):
1814
+ arg_types = {"this": True, "expressions": False}
1815
+
1816
+
1805
1817
  class Opclass(Expression):
1806
1818
  arg_types = {"this": True, "expression": True}
1807
1819
 
1808
1820
 
1809
1821
  class Star(Expression):
1810
- arg_types = {"except_": False, "replace": False, "rename": False}
1822
+ arg_types = {"except_": False, "replace": False, "rename": False, "ilike": False}
1811
1823
 
1812
1824
  @property
1813
1825
  def name(self) -> str:
@@ -2415,7 +2427,8 @@ def convert(value: t.Any, copy: bool = False) -> Expr:
2415
2427
 
2416
2428
  return _Array(expressions=[convert(v, copy=copy) for v in value])
2417
2429
  if isinstance(value, dict):
2418
- from sqlglot.expressions.array import Array as _Array, Map as _Map
2430
+ from sqlglot.expressions.array import Array as _Array
2431
+ from sqlglot.expressions.array import Map as _Map
2419
2432
 
2420
2433
  return _Map(
2421
2434
  keys=_Array(expressions=[convert(k, copy=copy) for k in value]),
@@ -370,6 +370,14 @@ class DropPrimaryKey(Expression):
370
370
  arg_types = {}
371
371
 
372
372
 
373
+ class Undrop(Expression):
374
+ arg_types = {"this": True, "kind": True, "rename": False}
375
+
376
+ @property
377
+ def kind(self) -> str:
378
+ return self.args["kind"].upper()
379
+
380
+
373
381
  class Command(Expression):
374
382
  arg_types = {"this": True, "expression": False}
375
383
 
@@ -62,6 +62,10 @@ class JSONBDeleteAtPath(Expression, Binary, Func):
62
62
  pass
63
63
 
64
64
 
65
+ class JSONBPathExists(Expression, Binary, Predicate, Func):
66
+ pass
67
+
68
+
65
69
  class JSONBExists(Expression, Func):
66
70
  arg_types = {"this": True, "path": True}
67
71
  _sql_names = ["JSONB_EXISTS"]
@@ -5,8 +5,8 @@ from __future__ import annotations
5
5
  import typing as t
6
6
  from enum import auto
7
7
 
8
+ from sqlglot.expressions.core import ColumnConstraintKind, Expression, Literal, convert
8
9
  from sqlglot.helper import AutoName
9
- from sqlglot.expressions.core import Expression, ColumnConstraintKind, Literal, convert
10
10
 
11
11
 
12
12
  class Property(Expression):
@@ -63,6 +63,10 @@ class BlockCompressionProperty(Property):
63
63
  }
64
64
 
65
65
 
66
+ class CalledOnNullInputProperty(Property):
67
+ arg_types = {}
68
+
69
+
66
70
  class CatalogProperty(Property):
67
71
  arg_types = {}
68
72
 
@@ -1967,7 +1967,7 @@ class JSON(Expression):
1967
1967
 
1968
1968
 
1969
1969
  class JSONPath(Expression):
1970
- arg_types = {"expressions": True, "escape": False}
1970
+ arg_types = {"expressions": True}
1971
1971
 
1972
1972
  @property
1973
1973
  def output_name(self) -> str:
@@ -1984,7 +1984,7 @@ class JSONPathFilter(JSONPathPart):
1984
1984
 
1985
1985
 
1986
1986
  class JSONPathKey(JSONPathPart):
1987
- arg_types = {"this": True}
1987
+ arg_types = {"this": True, "quoted": False}
1988
1988
 
1989
1989
 
1990
1990
  class JSONPathRecursive(JSONPathPart):
@@ -437,8 +437,12 @@ class FromISO8601Timestamp(Expression, Func):
437
437
  _sql_names = ["FROM_ISO8601_TIMESTAMP"]
438
438
 
439
439
 
440
+ class FromISO8601Date(Expression, Func):
441
+ _sql_names = ["FROM_ISO8601_DATE"]
442
+
443
+
440
444
  class ParseDatetime(Expression, Func):
441
- arg_types = {"this": True, "format": False, "zone": False}
445
+ arg_types = {"this": True, "format": False, "zone": False, "default_year": False}
442
446
 
443
447
 
444
448
  class ParseTime(Expression, Func):
@@ -9,6 +9,7 @@ from functools import reduce, wraps
9
9
  from sqlglot import exp
10
10
  from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
11
11
  from sqlglot.expressions import apply_index_offset
12
+ from sqlglot.expressions.core import maybe_parse
12
13
  from sqlglot.helper import csv, name_sequence, seq_get
13
14
  from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS
14
15
  from sqlglot.time import format_time
@@ -139,6 +140,7 @@ class Generator:
139
140
  ),
140
141
  exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
141
142
  exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
143
+ exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
142
144
  exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
143
145
  exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
144
146
  exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
@@ -147,6 +149,7 @@ class Generator:
147
149
  exp.CaseSpecificColumnConstraint: lambda _, e: (
148
150
  f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
149
151
  ),
152
+ exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
150
153
  exp.Ceil: lambda self, e: self.ceil_floor(e),
151
154
  exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
152
155
  exp.CharacterSetProperty: lambda self, e: (
@@ -203,6 +206,7 @@ class Generator:
203
206
  exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
204
207
  exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
205
208
  exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
209
+ exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
206
210
  exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
207
211
  exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
208
212
  exp.LanguageProperty: lambda self, e: self.naked_property(e),
@@ -489,6 +493,12 @@ class Generator:
489
493
  # Whether to escape keys using single quotes in JSON paths
490
494
  JSON_PATH_SINGLE_QUOTE_ESCAPE = False
491
495
 
496
+ # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
497
+ # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
498
+ # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
499
+ # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
500
+ JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
501
+
492
502
  # The JSONPathPart expressions supported by this dialect
493
503
  SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
494
504
 
@@ -668,6 +678,7 @@ class Generator:
668
678
  exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
669
679
  exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
670
680
  exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
681
+ exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
671
682
  exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
672
683
  exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
673
684
  exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
@@ -1007,23 +1018,24 @@ class Generator:
1007
1018
  if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1008
1019
  return sql
1009
1020
 
1010
- comments_sql = " ".join(
1011
- f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1012
- )
1021
+ comments_list = [
1022
+ f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1023
+ for comment in comments
1024
+ if comment
1025
+ ]
1013
1026
 
1014
- if not comments_sql:
1027
+ if not comments_list:
1015
1028
  return sql
1016
1029
 
1017
- comments_sql = self._replace_line_breaks(comments_sql)
1018
-
1019
1030
  if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1031
+ comments_sql = self.sep().join(comments_list)
1020
1032
  return (
1021
1033
  f"{self.sep()}{comments_sql}{sql}"
1022
1034
  if not sql or sql[0].isspace()
1023
1035
  else f"{comments_sql}{self.sep()}{sql}"
1024
1036
  )
1025
1037
 
1026
- return f"{sql} {comments_sql}"
1038
+ return f"{sql} {' '.join(comments_list)}"
1027
1039
 
1028
1040
  def wrap(self, expression: exp.Expr | str) -> str:
1029
1041
  this_sql = (
@@ -1945,6 +1957,20 @@ class Generator:
1945
1957
  params = self.sql(expression, "params")
1946
1958
  return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1947
1959
 
1960
+ def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
1961
+ this = expression.this
1962
+ if this and this.is_string:
1963
+ resolved = maybe_parse(this.name).sql(self.dialect)
1964
+ if "expressions" in expression.args:
1965
+ # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
1966
+ # We can't safely emit the call to other dialects since name/arg semantics may differ
1967
+ self.unsupported(
1968
+ "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
1969
+ )
1970
+ return resolved
1971
+ self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
1972
+ return self.func("IDENTIFIER", this)
1973
+
1948
1974
  def identifier_sql(self, expression: exp.Identifier) -> str:
1949
1975
  text = expression.name
1950
1976
  lower = text.lower()
@@ -2393,10 +2419,12 @@ class Generator:
2393
2419
  laterals = self.expressions(expression, key="laterals", sep="")
2394
2420
 
2395
2421
  file_format = self.sql(expression, "format")
2422
+ pattern = self.sql(expression, "pattern")
2396
2423
  if file_format:
2397
- pattern = self.sql(expression, "pattern")
2398
2424
  pattern = f", PATTERN => {pattern}" if pattern else ""
2399
2425
  file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2426
+ elif pattern:
2427
+ file_format = f" (PATTERN => {pattern})"
2400
2428
 
2401
2429
  ordinality = expression.args.get("ordinality") or ""
2402
2430
  if ordinality:
@@ -3371,7 +3399,9 @@ class Generator:
3371
3399
  replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3372
3400
  rename = self.expressions(expression, key="rename", flat=True)
3373
3401
  rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3374
- return f"*{except_}{replace}{rename}"
3402
+ ilike = self.sql(expression, "ilike")
3403
+ ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3404
+ return f"*{ilike}{except_}{replace}{rename}"
3375
3405
 
3376
3406
  def parameter_sql(self, expression: exp.Parameter) -> str:
3377
3407
  this = self.sql(expression, "this")
@@ -3719,9 +3749,6 @@ class Generator:
3719
3749
  def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3720
3750
  path = self.expressions(expression, sep="", flat=True).lstrip(".")
3721
3751
 
3722
- if expression.args.get("escape"):
3723
- path = self.escape_str(path)
3724
-
3725
3752
  if self.QUOTE_JSON_PATH:
3726
3753
  path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3727
3754
 
@@ -3975,6 +4002,12 @@ class Generator:
3975
4002
  zone = self.sql(expression, "zone")
3976
4003
  return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3977
4004
 
4005
+ def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4006
+ return self.sql(exp.cast(expression.this, exp.DType.DATE))
4007
+
4008
+ def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4009
+ return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4010
+
3978
4011
  def add_sql(self, expression: exp.Add) -> str:
3979
4012
  return self.binary(expression, "+")
3980
4013
 
@@ -4054,6 +4087,14 @@ class Generator:
4054
4087
  def strtotime_sql(self, expression: exp.StrToTime) -> str:
4055
4088
  return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4056
4089
 
4090
+ def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4091
+ return self.func(
4092
+ "PARSE_DATETIME",
4093
+ expression.this,
4094
+ expression.args.get("format"),
4095
+ expression.args.get("zone"),
4096
+ )
4097
+
4057
4098
  def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4058
4099
  zone = self.sql(expression, "this")
4059
4100
  return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
@@ -5228,10 +5269,20 @@ class Generator:
5228
5269
  this = self.json_path_part(this)
5229
5270
  return f".{this}" if this else ""
5230
5271
 
5231
- if self.SAFE_JSON_PATH_KEY_RE.match(this):
5272
+ quoted = expression.args.get("quoted")
5273
+ if not (
5274
+ quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5275
+ ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5232
5276
  return f".{this}"
5233
5277
 
5234
5278
  this = self.json_path_part(this)
5279
+
5280
+ if quoted and self.QUOTE_JSON_PATH:
5281
+ # The whole path is rendered as a single quoted string literal, so the bracketed key
5282
+ # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be
5283
+ # escaped again for the outer string literal (-> ["x \\"y\\"z"]).
5284
+ this = self.escape_str(this)
5285
+
5235
5286
  return (
5236
5287
  f"[{this}]"
5237
5288
  if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
@@ -280,6 +280,7 @@ class ClickHouseGenerator(generator.Generator):
280
280
  exp.ArrayConcat: rename_func("arrayConcat"),
281
281
  exp.ArrayContains: rename_func("has"),
282
282
  exp.ArrayFilter: lambda self, e: self.func("arrayFilter", e.expression, e.this),
283
+ exp.Transform: lambda self, e: self.func("arrayMap", e.expression, e.this),
283
284
  exp.ArrayRemove: remove_from_array_using_filter,
284
285
  exp.ArrayReverse: rename_func("arrayReverse"),
285
286
  exp.ArraySlice: rename_func("arraySlice"),
@@ -377,7 +378,9 @@ class ClickHouseGenerator(generator.Generator):
377
378
  exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
378
379
  rename_func("editDistance")
379
380
  ),
380
- exp.ParseDatetime: rename_func("parseDateTime"),
381
+ exp.ParseDatetime: lambda self, e: self.func(
382
+ "parseDateTime", e.this, e.args.get("format"), e.args.get("zone")
383
+ ),
381
384
  }
382
385
 
383
386
  PROPERTIES_LOCATION = {
@@ -15,6 +15,7 @@ class DatabricksGenerator(SparkGenerator):
15
15
  COPY_PARAMS_ARE_WRAPPED = False
16
16
  COPY_PARAMS_EQ_REQUIRED = True
17
17
  JSON_PATH_SINGLE_QUOTE_ESCAPE = False
18
+ JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = True
18
19
  SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
19
20
  QUOTE_JSON_PATH = False
20
21
  PARSE_JSON_NAME: str | None = "PARSE_JSON"
@@ -6,7 +6,6 @@ import re
6
6
  import typing as t
7
7
 
8
8
  from sqlglot import exp, generator, transforms
9
-
10
9
  from sqlglot.dialects.dialect import (
11
10
  DATETIME_DELTA,
12
11
  JSON_EXTRACT_TYPE,
@@ -38,9 +37,12 @@ from sqlglot.dialects.dialect import (
38
37
  unit_to_str,
39
38
  )
40
39
  from sqlglot.generator import unsupported_args
41
- from sqlglot.helper import is_date_unit, seq_get
40
+ from sqlglot.helper import find_new_name, is_date_unit, seq_get
41
+ from sqlglot.optimizer.scope import find_all_in_scope
42
42
  from builtins import type as Type
43
43
 
44
+ _CONNECT_BY_ARGS_TO_SKIP = frozenset({"connect", "where", "from_", "with_", "expressions"})
45
+
44
46
  # Regex to detect time zones in timestamps of the form [+|-]TT[:tt]
45
47
  # The pattern matches timezone offsets that appear after the time portion
46
48
  TIMEZONE_PATTERN = re.compile(r":\d{2}.*?[+\-]\d{2}(?::\d{2})?")
@@ -710,6 +712,124 @@ def _seq_to_range_in_generator(expression: exp.Expr) -> exp.Expr:
710
712
  return expression.transform(replace_seq, copy=False)
711
713
 
712
714
 
715
+ def connect_by_to_recursive_cte(expression: exp.Expr) -> exp.Expr:
716
+ # Rewrites START WITH ... CONNECT BY PRIOR into WITH RECURSIVE
717
+ # Falls through unchanged if there are no PRIORs.
718
+ if not isinstance(expression, exp.Select) or not expression.args.get("connect"):
719
+ return expression
720
+
721
+ connect = expression.args["connect"]
722
+ connect_pred = connect.args["connect"]
723
+
724
+ priors = list(connect_pred.find_all(exp.Prior))
725
+ if not priors:
726
+ return expression
727
+
728
+ from_ = expression.args.get("from_")
729
+ if not from_ or expression.args.get("joins"):
730
+ return expression
731
+
732
+ source_table = from_.this
733
+ base_select_exprs = expression.expressions
734
+ base_where = expression.args.get("where")
735
+ base_with = expression.args.get("with_")
736
+
737
+ # LEVEL is a Snowflake pseudo-column: it's always computed as a depth counter in the CTE.
738
+ has_level = any(
739
+ isinstance(col, exp.Column) and col.name.upper() == "LEVEL"
740
+ for e in base_select_exprs
741
+ for col in e.find_all(exp.Column)
742
+ )
743
+ has_star = expression.is_star
744
+
745
+ # CONNECT_BY_ROOT col yields the value of `col` from the START WITH row that begins each
746
+ # branch. Each one is threaded through the CTE as an extra column: the anchor binds it to the
747
+ # row's own value, the recursive arm forwards the parent's value unchanged.
748
+ root_col_names: list[str] = []
749
+ anchor_root_cols: list[exp.Expr] = []
750
+ inner_root_cols: list[exp.Expr] = []
751
+ roots = [root for e in base_select_exprs for root in e.find_all(exp.ConnectByRoot)]
752
+
753
+ for i, root in enumerate(roots):
754
+ name = f"_connect_by_root_{i}"
755
+ root_col_names.append(name)
756
+ anchor_root_cols.append(exp.alias_(root.this, name))
757
+ inner_root_cols.append(exp.alias_(exp.column(name, "_parent_row"), name))
758
+ root.replace(exp.column(name))
759
+
760
+ # Build the join condition from the full CONNECT BY predicate:
761
+ # PRIOR(col) → _parent_row.col, unqualified cols → _child_row.col.
762
+ def _qualify_connect_pred(node: exp.Expression) -> exp.Expression:
763
+ for col in find_all_in_scope(node, exp.Column):
764
+ col.set(
765
+ "table",
766
+ exp.to_identifier(
767
+ "_parent_row" if isinstance(col.parent, exp.Prior) else "_child_row"
768
+ ),
769
+ )
770
+ for prior in find_all_in_scope(node, exp.Prior):
771
+ prior.replace(prior.this)
772
+ return node
773
+
774
+ # Avoid colliding with any CTE names already on the query.
775
+ cte_name = find_new_name(
776
+ {cte.alias for cte in (base_with.expressions if base_with else [])}, "_rootcte"
777
+ )
778
+
779
+ # Anchor: project all source columns + seed LEVEL at 1 + bind each root column to its own value.
780
+ anchor = exp.select(
781
+ exp.Star(), exp.alias_(exp.Literal.number(1), "level"), *anchor_root_cols
782
+ ).from_(source_table)
783
+ if connect.args.get("start"):
784
+ anchor = anchor.where(connect.args["start"])
785
+
786
+ # Recursive arm: carry all child columns + increment level + forward each root value.
787
+ # SELECT * in both arms means WHERE/PRIOR columns are always available without explicit tracking.
788
+ inner_query = (
789
+ exp.select(
790
+ exp.Column(this=exp.Star(), table=exp.to_identifier("_child_row")),
791
+ exp.alias_(exp.column("level", "_parent_row") + 1, "level"),
792
+ *inner_root_cols,
793
+ )
794
+ .from_(source_table.as_("_child_row"))
795
+ .join(exp.to_table(cte_name).as_("_parent_row"), on=_qualify_connect_pred(connect_pred))
796
+ )
797
+
798
+ # Outer SELECT re-projects from the CTE. Synthetic level/root columns are excluded from any
799
+ # star expansion (level only when not referenced) but kept where explicitly projected.
800
+ if has_star:
801
+ except_cols = [] if has_level else [exp.column("level")]
802
+ except_cols.extend(exp.column(name) for name in root_col_names)
803
+ star = exp.Star(except_=except_cols) if except_cols else exp.Star()
804
+ outer_select_exprs: list[exp.Expr] = [
805
+ star,
806
+ *(e for e in base_select_exprs if not e.is_star),
807
+ ]
808
+ else:
809
+ outer_select_exprs = base_select_exprs
810
+ outer_query = exp.select(*outer_select_exprs).from_(cte_name)
811
+ if base_where:
812
+ outer_query = outer_query.where(base_where.this)
813
+
814
+ # Attach the CTE, marking the WITH clause recursive.
815
+ if base_with:
816
+ outer_query.set("with_", base_with)
817
+ outer_query = outer_query.with_(
818
+ cte_name, as_=anchor.union(inner_query, distinct=False), recursive=True, copy=False
819
+ )
820
+
821
+ for arg, val in expression.args.items():
822
+ if val and arg not in _CONNECT_BY_ARGS_TO_SKIP:
823
+ outer_query.set(arg, val)
824
+
825
+ # Strip stale source table qualifiers in one pass; CTEs are child scopes so
826
+ # find_all_in_scope stays within the outer query only.
827
+ for col in find_all_in_scope(outer_query, exp.Column):
828
+ col.set("table", None)
829
+
830
+ return outer_query
831
+
832
+
713
833
  def _seq_sql(self: DuckDBGenerator, expression: exp.Func, byte_width: int) -> str:
714
834
  """
715
835
  Transpile Snowflake SEQ1/SEQ2/SEQ4/SEQ8 to DuckDB.
@@ -1616,7 +1736,9 @@ class DuckDBGenerator(generator.Generator):
1616
1736
  exp.Lateral: _explode_to_unnest_sql,
1617
1737
  exp.LogicalOr: lambda self, e: self.func("BOOL_OR", _cast_to_boolean(e.this)),
1618
1738
  exp.LogicalAnd: lambda self, e: self.func("BOOL_AND", _cast_to_boolean(e.this)),
1619
- exp.Select: transforms.preprocess([_seq_to_range_in_generator]),
1739
+ exp.Select: transforms.preprocess(
1740
+ [connect_by_to_recursive_cte, _seq_to_range_in_generator]
1741
+ ),
1620
1742
  exp.Seq1: lambda self, e: _seq_sql(self, e, 1),
1621
1743
  exp.Seq2: lambda self, e: _seq_sql(self, e, 2),
1622
1744
  exp.Seq4: lambda self, e: _seq_sql(self, e, 4),
@@ -2577,9 +2699,6 @@ class DuckDBGenerator(generator.Generator):
2577
2699
  )
2578
2700
  return self.function_fallback_sql(expression)
2579
2701
 
2580
- def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
2581
- return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
2582
-
2583
2702
  def strposition_sql(self, expression: exp.StrPosition) -> str:
2584
2703
  this = expression.this
2585
2704
  substr = expression.args.get("substr")
@@ -2659,6 +2778,19 @@ class DuckDBGenerator(generator.Generator):
2659
2778
  )
2660
2779
  )
2661
2780
 
2781
+ def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
2782
+ formatted_time = self.format_time(expression)
2783
+
2784
+ default_year = expression.args.get("default_year")
2785
+ if default_year:
2786
+ year_str = exp.Literal.string(f"{default_year.name} ")
2787
+ fmt_prefix = exp.Literal.string("%Y ")
2788
+ value = exp.DPipe(this=year_str, expression=expression.this)
2789
+ fmt = exp.DPipe(this=fmt_prefix, expression=formatted_time)
2790
+ return self.func("STRPTIME", value, fmt)
2791
+
2792
+ return self.func("STRPTIME", expression.this, formatted_time)
2793
+
2662
2794
  def parsetime_sql(self, expression: exp.ParseTime) -> str:
2663
2795
  formatted_time = self.format_time(expression)
2664
2796
  return self.sql(
@@ -4370,6 +4502,23 @@ class DuckDBGenerator(generator.Generator):
4370
4502
  )
4371
4503
  .else_(exp.TryCast(this=src, to=to))
4372
4504
  )
4505
+ elif (
4506
+ isinstance(to_type, exp.Interval)
4507
+ and (unit := to_type.unit)
4508
+ and expression.args.get("requires_string")
4509
+ ):
4510
+ interval_type = exp.DataType.build("INTERVAL")
4511
+ if isinstance(unit, exp.IntervalSpan):
4512
+ self.unsupported(
4513
+ "TRY_CAST to INTERVAL with span (e.g. HOUR TO MINUTE) is not supported in DuckDB"
4514
+ )
4515
+ return self.sql(exp.TryCast(this=src, to=interval_type))
4516
+ return self.sql(
4517
+ exp.TryCast(
4518
+ this=exp.DPipe(this=src, expression=exp.Literal.string(f" {unit.name}")),
4519
+ to=interval_type,
4520
+ )
4521
+ )
4373
4522
 
4374
4523
  return super().trycast_sql(expression)
4375
4524
 
@@ -348,9 +348,7 @@ class ExasolGenerator(generator.Generator):
348
348
  exp.DateTrunc: _date_trunc_sql,
349
349
  exp.DayOfWeek: lambda self, e: f"CAST(TO_CHAR({self.sql(e, 'this')}, 'D') AS INTEGER)",
350
350
  exp.DatetimeTrunc: timestamptrunc_sql(),
351
- exp.GroupConcat: lambda self, e: groupconcat_sql(
352
- self, e, func_name="LISTAGG", within_group=True
353
- ),
351
+ exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, on_overflow=True),
354
352
  # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/edit_distance.htm#EDIT_DISTANCE
355
353
  exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
356
354
  rename_func("EDIT_DISTANCE")
@@ -653,6 +653,10 @@ class MySQLGenerator(generator.Generator):
653
653
  self.unsupported("Array operations are not supported by MySQL")
654
654
  return self.function_fallback_sql(expression)
655
655
 
656
+ def arraycontainedby_sql(self, expression: exp.ArrayContainedBy) -> str:
657
+ self.unsupported("Array operations are not supported by MySQL")
658
+ return self.function_fallback_sql(expression)
659
+
656
660
  def dpipe_sql(self, expression: exp.DPipe) -> str:
657
661
  return self.func("CONCAT", *expression.flatten())
658
662