sqlglotc 30.10.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.10.0/sqlglotc.egg-info → sqlglotc-30.12.0}/PKG-INFO +4 -2
  2. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/pyproject.toml +3 -3
  3. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/setup.py +33 -0
  4. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/array.py +4 -0
  5. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/builders.py +6 -1
  6. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/core.py +31 -10
  7. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/ddl.py +8 -0
  8. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/json.py +4 -0
  9. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/properties.py +5 -1
  10. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/query.py +2 -2
  11. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/temporal.py +5 -1
  12. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generator.py +66 -15
  13. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/bigquery.py +3 -3
  14. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/clickhouse.py +4 -1
  15. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/databricks.py +11 -12
  16. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/duckdb.py +155 -6
  17. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/exasol.py +1 -3
  18. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/mysql.py +4 -0
  19. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/postgres.py +1 -1
  20. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/presto.py +2 -0
  21. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/snowflake.py +15 -1
  22. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/starrocks.py +15 -1
  23. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/tsql.py +9 -0
  24. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/lineage.py +124 -20
  25. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/annotate_types.py +4 -4
  26. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/normalize_identifiers.py +4 -3
  27. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify_columns.py +44 -14
  28. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify_tables.py +7 -1
  29. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/resolver.py +10 -4
  30. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/scope.py +32 -8
  31. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/simplify.py +76 -34
  32. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parser.py +63 -29
  33. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/bigquery.py +12 -7
  34. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/clickhouse.py +5 -0
  35. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/databricks.py +1 -0
  36. sqlglotc-30.12.0/sqlglot/parsers/druid.py +10 -0
  37. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/exasol.py +3 -2
  38. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/postgres.py +1 -0
  39. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/snowflake.py +75 -3
  40. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/starrocks.py +4 -1
  41. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/tokenizer_core.py +2 -0
  42. {sqlglotc-30.10.0 → sqlglotc-30.12.0/sqlglotc.egg-info}/PKG-INFO +4 -2
  43. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/SOURCES.txt +2 -0
  44. sqlglotc-30.12.0/sqlglotc.egg-info/requires.txt +6 -0
  45. sqlglotc-30.12.0/sqlglotc.egg-info/scm_file_list.json +7 -0
  46. sqlglotc-30.12.0/sqlglotc.egg-info/scm_version.json +8 -0
  47. sqlglotc-30.10.0/sqlglot/parsers/druid.py +0 -7
  48. sqlglotc-30.10.0/sqlglotc.egg-info/requires.txt +0 -5
  49. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/MANIFEST.in +0 -0
  50. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/setup.cfg +0 -0
  51. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/errors.py +0 -0
  52. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/executor/table.py +0 -0
  53. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/aggregate.py +0 -0
  54. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/constraints.py +0 -0
  55. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/datatypes.py +0 -0
  56. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/dml.py +0 -0
  57. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/functions.py +0 -0
  58. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/math.py +0 -0
  59. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/expressions/string.py +0 -0
  60. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/athena.py +0 -0
  61. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/doris.py +0 -0
  62. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/dremio.py +0 -0
  63. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/drill.py +0 -0
  64. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/druid.py +0 -0
  65. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/dune.py +0 -0
  66. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/fabric.py +0 -0
  67. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/hive.py +0 -0
  68. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/materialize.py +0 -0
  69. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/oracle.py +0 -0
  70. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/prql.py +0 -0
  71. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/python.py +0 -0
  72. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/redshift.py +0 -0
  73. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/risingwave.py +0 -0
  74. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/singlestore.py +0 -0
  75. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/solr.py +0 -0
  76. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/spark.py +0 -0
  77. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/spark2.py +0 -0
  78. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/sqlite.py +0 -0
  79. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/tableau.py +0 -0
  80. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/teradata.py +0 -0
  81. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/generators/trino.py +0 -0
  82. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/helper.py +0 -0
  83. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  84. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  85. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify.py +0 -0
  86. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/athena.py +0 -0
  87. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/base.py +0 -0
  88. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/doris.py +0 -0
  89. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/dremio.py +0 -0
  90. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/drill.py +0 -0
  91. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/duckdb.py +0 -0
  92. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/dune.py +0 -0
  93. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/fabric.py +0 -0
  94. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/hive.py +0 -0
  95. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/materialize.py +0 -0
  96. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/mysql.py +0 -0
  97. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/oracle.py +0 -0
  98. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/presto.py +0 -0
  99. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/prql.py +0 -0
  100. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/redshift.py +0 -0
  101. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/risingwave.py +0 -0
  102. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/singlestore.py +0 -0
  103. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/solr.py +0 -0
  104. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/spark.py +0 -0
  105. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/spark2.py +0 -0
  106. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/sqlite.py +0 -0
  107. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/tableau.py +0 -0
  108. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/teradata.py +0 -0
  109. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/trino.py +0 -0
  110. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/parsers/tsql.py +0 -0
  111. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/schema.py +0 -0
  112. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/serde.py +0 -0
  113. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/time.py +0 -0
  114. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglot/trie.py +0 -0
  115. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  116. {sqlglotc-30.10.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/top_level.txt +0 -0
@@ -1,13 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.10.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.12.0
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
12
13
  Requires-Dist: setuptools_scm; extra == "dev"
13
- Requires-Dist: sqlglot-mypy>=2.1.0.post2; extra == "dev"
14
+ Requires-Dist: sqlglot-mypy>=2.1.0.post3; extra == "dev"
15
+ Dynamic: requires-dist
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "sqlglotc"
3
- dynamic = ["version"]
3
+ dynamic = ["version", "dependencies"]
4
4
  description = "mypyc-compiled extensions for sqlglot"
5
5
  authors = [{ name = "Toby Mao", email = "toby.mao@gmail.com" }]
6
6
  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
  ]
@@ -128,9 +128,42 @@ class sdist(_sdist):
128
128
  shutil.rmtree(local_sqlglot, ignore_errors=True)
129
129
 
130
130
 
131
+ def _sqlglot_requirement():
132
+ """Pin sqlglot to the matching version so resolvers keep the two packages in lockstep."""
133
+ try:
134
+ pkg_info = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PKG-INFO")
135
+ if os.path.isfile(pkg_info):
136
+ # Building from an sdist: no git metadata; the released version is frozen in PKG-INFO.
137
+ with open(pkg_info, encoding="utf-8") as fd:
138
+ version = next(
139
+ (line.split(":", 1)[1].strip() for line in fd if line.startswith("Version:")),
140
+ "",
141
+ )
142
+ else:
143
+ from setuptools_scm import get_version
144
+
145
+ version = get_version(
146
+ root="..",
147
+ relative_to=__file__,
148
+ local_scheme="no-local-version",
149
+ fallback_version="0.0.0",
150
+ )
151
+ except Exception as e:
152
+ print(f"sqlglotc: failed to determine the sqlglot version to pin: {e}")
153
+ version = ""
154
+
155
+ # Dev and fallback (no git metadata) builds have no matching sqlglot release on PyPI;
156
+ # depend on sqlglot unpinned since the extensions can't be imported without it.
157
+ if version in ("", "0.0.0") or ".dev" in version:
158
+ return ["sqlglot"]
159
+
160
+ return [f"sqlglot=={version}"]
161
+
162
+
131
163
  setup(
132
164
  name="sqlglotc",
133
165
  packages=[],
166
+ install_requires=_sqlglot_requirement(),
134
167
  ext_modules=mypycify(
135
168
  _source_paths(), opt_level=os.environ.get("MYPYC_OPT", "2"), separate=True, verbose=True
136
169
  ),
@@ -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
 
@@ -319,6 +319,11 @@ def parse_identifier(name: str | Identifier, dialect: DialectType = None) -> Ide
319
319
  Returns:
320
320
  The identifier ast node.
321
321
  """
322
+ if isinstance(name, str) and SAFE_IDENTIFIER_RE.match(name):
323
+ # Simple names parse to a single unquoted identifier in all dialects, so we can
324
+ # avoid the tokenizer/parser round-trip for them.
325
+ return Identifier(this=name, quoted=False)
326
+
322
327
  try:
323
328
  expression = maybe_parse(name, dialect=dialect, into=Identifier)
324
329
  except (ParseError, TokenError):
@@ -822,7 +827,7 @@ def replace_tables(
822
827
  mapping = {normalize_table_name(k, dialect=dialect): v for k, v in mapping.items()}
823
828
 
824
829
  def _replace_tables(node: Expr) -> Expr:
825
- if isinstance(node, Table) and node.meta.get("replace") is not False:
830
+ if isinstance(node, Table) and node.meta_get("replace") is not False:
826
831
  original = normalize_table_name(node, dialect=dialect)
827
832
  new_name = mapping.get(original)
828
833
 
@@ -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
 
@@ -240,6 +240,9 @@ class Expr:
240
240
  def meta(self) -> dict[str, t.Any]:
241
241
  raise NotImplementedError
242
242
 
243
+ def meta_get(self, key: str, default: t.Any = None) -> t.Any:
244
+ raise NotImplementedError
245
+
243
246
  def __deepcopy__(self, memo: t.Any) -> Expr:
244
247
  raise NotImplementedError
245
248
 
@@ -990,6 +993,11 @@ class Expression(Expr):
990
993
  self._meta = {}
991
994
  return self._meta
992
995
 
996
+ def meta_get(self, key: str, default: t.Any = None) -> t.Any:
997
+ """Reads a meta value without allocating the meta dict (unlike the `meta` property)."""
998
+ meta = self._meta
999
+ return meta.get(key, default) if meta is not None else default
1000
+
993
1001
  def __deepcopy__(self, memo: t.Any) -> Expr:
994
1002
  root = self.__class__()
995
1003
  stack: list[tuple[Expr, Expr]] = [(self, root)]
@@ -1781,7 +1789,12 @@ class JoinHint(Expression):
1781
1789
 
1782
1790
 
1783
1791
  class Identifier(Expression):
1784
- 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
+ }
1785
1798
  is_primitive = True
1786
1799
  _hash_raw_args = True
1787
1800
 
@@ -1794,12 +1807,19 @@ class Identifier(Expression):
1794
1807
  return self.name
1795
1808
 
1796
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
+
1797
1817
  class Opclass(Expression):
1798
1818
  arg_types = {"this": True, "expression": True}
1799
1819
 
1800
1820
 
1801
1821
  class Star(Expression):
1802
- arg_types = {"except_": False, "replace": False, "rename": False}
1822
+ arg_types = {"except_": False, "replace": False, "rename": False, "ilike": False}
1803
1823
 
1804
1824
  @property
1805
1825
  def name(self) -> str:
@@ -2407,7 +2427,8 @@ def convert(value: t.Any, copy: bool = False) -> Expr:
2407
2427
 
2408
2428
  return _Array(expressions=[convert(v, copy=copy) for v in value])
2409
2429
  if isinstance(value, dict):
2410
- 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
2411
2432
 
2412
2433
  return _Map(
2413
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"
@@ -4554,7 +4595,7 @@ class Generator:
4554
4595
  args.append(arg_value)
4555
4596
 
4556
4597
  if self.dialect.PRESERVE_ORIGINAL_NAMES:
4557
- name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4598
+ name = expression.meta_get("name") or expression.sql_name()
4558
4599
  else:
4559
4600
  name = expression.sql_name()
4560
4601
 
@@ -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
@@ -5258,7 +5309,7 @@ class Generator:
5258
5309
  )
5259
5310
  return self.sql(this)
5260
5311
 
5261
- if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
5312
+ if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5262
5313
  if self.IGNORE_NULLS_BEFORE_ORDER:
5263
5314
  # The first modifier here will be the one closest to the AggFunc's arg
5264
5315
  mods = sorted(
@@ -201,7 +201,7 @@ def _levenshtein_sql(self: BigQueryGenerator, expression: exp.Levenshtein) -> st
201
201
 
202
202
 
203
203
  def _json_extract_sql(self: BigQueryGenerator, expression: JSON_EXTRACT_TYPE) -> str:
204
- name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
204
+ name = expression.meta_get("name") or expression.sql_name()
205
205
  upper = name.upper()
206
206
 
207
207
  dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS
@@ -565,7 +565,7 @@ class BigQueryGenerator(generator.Generator):
565
565
  )
566
566
 
567
567
  def column_parts(self, expression: exp.Column) -> str:
568
- if expression.meta.get("quoted_column"):
568
+ if expression.meta_get("quoted_column"):
569
569
  # If a column reference is of the form `dataset.table`.name, we need
570
570
  # to preserve the quoted table path, otherwise the reference breaks
571
571
  table_parts = ".".join(p.name for p in expression.parts[:-1])
@@ -583,7 +583,7 @@ class BigQueryGenerator(generator.Generator):
583
583
  #
584
584
  # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y` -> cross join
585
585
  # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
586
- if expression.meta.get("quoted_table"):
586
+ if expression.meta_get("quoted_table"):
587
587
  table_parts = ".".join(p.name for p in expression.parts)
588
588
  return self.sql(exp.Identifier(this=table_parts, quoted=True))
589
589
 
@@ -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 = {
@@ -10,19 +10,12 @@ from sqlglot.dialects.dialect import (
10
10
  from sqlglot.generators.spark import SparkGenerator
11
11
 
12
12
 
13
- def _jsonextract_sql(
14
- self: DatabricksGenerator, expression: exp.JSONExtract | exp.JSONExtractScalar
15
- ) -> str:
16
- this = self.sql(expression, "this")
17
- expr = self.sql(expression, "expression")
18
- return f"{this}:{expr}"
19
-
20
-
21
13
  class DatabricksGenerator(SparkGenerator):
22
14
  TABLESAMPLE_SEED_KEYWORD = "REPEATABLE"
23
15
  COPY_PARAMS_ARE_WRAPPED = False
24
16
  COPY_PARAMS_EQ_REQUIRED = True
25
17
  JSON_PATH_SINGLE_QUOTE_ESCAPE = False
18
+ JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = True
26
19
  SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
27
20
  QUOTE_JSON_PATH = False
28
21
  PARSE_JSON_NAME: str | None = "PARSE_JSON"
@@ -52,9 +45,10 @@ class DatabricksGenerator(SparkGenerator):
52
45
  transforms.any_to_exists,
53
46
  ]
54
47
  ),
55
- exp.JSONExtract: _jsonextract_sql,
56
- exp.JSONExtractScalar: _jsonextract_sql,
57
- exp.JSONPathRoot: lambda *_: "",
48
+ exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}:{self.sql(e, 'expression')}",
49
+ exp.JSONPathRoot: lambda self, e: (
50
+ "$" if isinstance(e.parent and e.parent.parent, exp.JSONExtractScalar) else ""
51
+ ),
58
52
  exp.ToChar: lambda self, e: (
59
53
  self.cast_sql(exp.Cast(this=e.this, to=exp.DataType(this="STRING")))
60
54
  if e.args.get("is_numeric")
@@ -98,7 +92,12 @@ class DatabricksGenerator(SparkGenerator):
98
92
 
99
93
  def jsonpath_sql(self, expression: exp.JSONPath) -> str:
100
94
  expression.set("escape", None)
101
- return super().jsonpath_sql(expression)
95
+ path = super().jsonpath_sql(expression)
96
+
97
+ if isinstance(expression.parent, exp.JSONExtractScalar):
98
+ return f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
99
+
100
+ return path
102
101
 
103
102
  def uniform_sql(self, expression: exp.Uniform) -> str:
104
103
  gen = expression.args.get("gen")