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.
- {sqlglotc-30.11.0/sqlglotc.egg-info → sqlglotc-30.12.0}/PKG-INFO +3 -3
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/pyproject.toml +2 -2
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/array.py +4 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/core.py +23 -10
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/ddl.py +8 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/json.py +4 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/properties.py +5 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/query.py +2 -2
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/temporal.py +5 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generator.py +64 -13
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/clickhouse.py +4 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/databricks.py +1 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/duckdb.py +155 -6
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/exasol.py +1 -3
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/mysql.py +4 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/postgres.py +1 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/presto.py +2 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/snowflake.py +15 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/starrocks.py +15 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify_columns.py +21 -3
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify_tables.py +7 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/resolver.py +4 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/simplify.py +57 -22
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parser.py +63 -29
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/bigquery.py +8 -3
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/clickhouse.py +5 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/databricks.py +1 -0
- sqlglotc-30.12.0/sqlglot/parsers/druid.py +10 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/exasol.py +3 -2
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/postgres.py +1 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/snowflake.py +75 -3
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/starrocks.py +4 -1
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/tokenizer_core.py +2 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0/sqlglotc.egg-info}/PKG-INFO +3 -3
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/SOURCES.txt +2 -0
- sqlglotc-30.12.0/sqlglotc.egg-info/requires.txt +6 -0
- sqlglotc-30.12.0/sqlglotc.egg-info/scm_file_list.json +7 -0
- sqlglotc-30.12.0/sqlglotc.egg-info/scm_version.json +8 -0
- sqlglotc-30.11.0/sqlglot/parsers/druid.py +0 -7
- sqlglotc-30.11.0/sqlglotc.egg-info/requires.txt +0 -6
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/MANIFEST.in +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/setup.cfg +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/setup.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/errors.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/executor/table.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/aggregate.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/builders.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/constraints.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/datatypes.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/dml.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/functions.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/math.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/expressions/string.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/athena.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/bigquery.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/doris.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/dremio.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/drill.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/druid.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/dune.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/fabric.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/hive.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/materialize.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/oracle.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/prql.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/python.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/redshift.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/risingwave.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/singlestore.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/solr.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/spark.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/spark2.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/sqlite.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/tableau.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/teradata.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/trino.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/generators/tsql.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/helper.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/lineage.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/annotate_types.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/qualify.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/optimizer/scope.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/athena.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/base.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/doris.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/dremio.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/drill.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/duckdb.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/dune.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/fabric.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/hive.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/materialize.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/mysql.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/oracle.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/presto.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/prql.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/redshift.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/risingwave.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/singlestore.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/solr.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/spark.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/spark2.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/sqlite.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/tableau.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/teradata.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/trino.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/parsers/tsql.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/schema.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/serde.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/time.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglot/trie.py +0 -0
- {sqlglotc-30.11.0 → sqlglotc-30.12.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 = {
|
|
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
|
|
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
|
|
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
|
-
|
|
1011
|
-
f"/*{self.sanitize_comment(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
|
|
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} {
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
|