sqlglotc 30.25.0__tar.gz → 30.26.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.25.0/sqlglotc.egg-info → sqlglotc-30.26.0}/PKG-INFO +3 -3
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/pyproject.toml +2 -2
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/core.py +4 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/properties.py +4 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generator.py +60 -4
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/duckdb.py +39 -7
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/postgres.py +32 -9
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/tsql.py +1 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/annotate_types.py +93 -87
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/resolver.py +3 -1
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/scope.py +7 -2
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parser.py +61 -15
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/clickhouse.py +1 -4
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/duckdb.py +14 -1
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/mysql.py +8 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/oracle.py +5 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/postgres.py +23 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/snowflake.py +37 -16
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/spark.py +1 -1
- {sqlglotc-30.25.0 → sqlglotc-30.26.0/sqlglotc.egg-info}/PKG-INFO +3 -3
- sqlglotc-30.26.0/sqlglotc.egg-info/requires.txt +6 -0
- sqlglotc-30.26.0/sqlglotc.egg-info/scm_version.json +8 -0
- sqlglotc-30.25.0/sqlglotc.egg-info/requires.txt +0 -6
- sqlglotc-30.25.0/sqlglotc.egg-info/scm_version.json +0 -8
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/MANIFEST.in +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/setup.cfg +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/setup.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/anonymize.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/errors.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/executor/table.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/aggregate.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/array.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/builders.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/constraints.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/datatypes.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/ddl.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/dml.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/functions.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/json.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/math.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/query.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/string.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/temporal.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/athena.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/bigquery.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/clickhouse.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/databricks.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/dax.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/doris.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/dremio.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/drill.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/druid.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/dune.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/exasol.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/fabric.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/hive.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/materialize.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/mysql.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/oracle.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/presto.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/prql.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/python.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/redshift.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/risingwave.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/singlestore.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/snowflake.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/solr.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/spark.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/spark2.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/sqlite.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/starrocks.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/tableau.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/teradata.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/trino.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/helper.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/lineage.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/qualify.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/qualify_columns.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/qualify_tables.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/simplify.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/athena.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/base.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/bigquery.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/databricks.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/dax.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/doris.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/dremio.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/drill.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/druid.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/dune.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/exasol.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/fabric.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/hive.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/materialize.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/presto.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/prql.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/redshift.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/risingwave.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/singlestore.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/solr.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/spark2.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/sqlite.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/starrocks.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/tableau.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/teradata.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/trino.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/tsql.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/serde.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/time.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/tokenizer_core.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/trie.py +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
- {sqlglotc-30.25.0 → sqlglotc-30.26.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.26.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.26.0
|
|
11
11
|
Provides-Extra: dev
|
|
12
12
|
Requires-Dist: setuptools>=61.0; extra == "dev"
|
|
13
|
-
Requires-Dist: setuptools_scm; extra == "dev"
|
|
13
|
+
Requires-Dist: setuptools_scm>=10.2; extra == "dev"
|
|
14
14
|
Requires-Dist: mypy>=2.4.0; extra == "dev"
|
|
15
15
|
Dynamic: requires-dist
|
|
@@ -7,7 +7,7 @@ license = "MIT"
|
|
|
7
7
|
requires-python = ">= 3.10"
|
|
8
8
|
|
|
9
9
|
[project.optional-dependencies]
|
|
10
|
-
dev = ["setuptools >= 61.0", "setuptools_scm", "mypy >= 2.4.0"]
|
|
10
|
+
dev = ["setuptools >= 61.0", "setuptools_scm >= 10.2", "mypy >= 2.4.0"]
|
|
11
11
|
|
|
12
12
|
[project.urls]
|
|
13
13
|
Homepage = "https://sqlglot.com/"
|
|
@@ -16,7 +16,7 @@ Repository = "https://github.com/tobymao/sqlglot"
|
|
|
16
16
|
[build-system]
|
|
17
17
|
requires = [
|
|
18
18
|
"setuptools >= 61.0",
|
|
19
|
-
"setuptools_scm",
|
|
19
|
+
"setuptools_scm >= 10.2",
|
|
20
20
|
"mypy >= 2.4.0",
|
|
21
21
|
"types-python-dateutil",
|
|
22
22
|
"sqlglot",
|
|
@@ -169,6 +169,7 @@ class Generator:
|
|
|
169
169
|
"CONVERT", e.this, e.args["dest"], e.args.get("source")
|
|
170
170
|
),
|
|
171
171
|
exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
|
|
172
|
+
exp.CopyTagsProperty: lambda *_: "COPY TAGS",
|
|
172
173
|
exp.CredentialsProperty: lambda self, e: (
|
|
173
174
|
f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
|
|
174
175
|
),
|
|
@@ -731,6 +732,7 @@ class Generator:
|
|
|
731
732
|
exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
|
|
732
733
|
exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
|
|
733
734
|
exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
|
|
735
|
+
exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA,
|
|
734
736
|
exp.Cluster: exp.Properties.Location.POST_SCHEMA,
|
|
735
737
|
exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
|
|
736
738
|
exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
|
|
@@ -1964,19 +1966,63 @@ class Generator:
|
|
|
1964
1966
|
return self.sql(select)
|
|
1965
1967
|
|
|
1966
1968
|
sqls: list[str] = []
|
|
1967
|
-
stack: list[str | exp.Expr] = [expression]
|
|
1969
|
+
stack: list[str | exp.Expr | int | None] = [expression]
|
|
1970
|
+
depth = 0
|
|
1971
|
+
open_parens = ""
|
|
1968
1972
|
|
|
1969
1973
|
while stack:
|
|
1970
1974
|
node = stack.pop()
|
|
1971
1975
|
|
|
1972
|
-
if isinstance(node,
|
|
1976
|
+
if isinstance(node, int):
|
|
1977
|
+
# Closes a plain grouping opened below, restoring its depth
|
|
1978
|
+
depth = node
|
|
1979
|
+
sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0)
|
|
1980
|
+
elif isinstance(node, exp.SetOperation):
|
|
1973
1981
|
stack.append(node.expression)
|
|
1974
1982
|
stack.append(
|
|
1975
1983
|
self.maybe_comment(
|
|
1976
1984
|
self.set_operation(node), comments=node.comments, separated=True
|
|
1977
1985
|
)
|
|
1978
1986
|
)
|
|
1979
|
-
|
|
1987
|
+
left = node.this
|
|
1988
|
+
if (
|
|
1989
|
+
not self.SET_OP_PARENTHESIZED_OPERANDS
|
|
1990
|
+
and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT
|
|
1991
|
+
):
|
|
1992
|
+
# Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper:
|
|
1993
|
+
# it renames duplicate columns and deepens recursion. Inner CTEs and
|
|
1994
|
+
# modifiers still need the wrapper.
|
|
1995
|
+
wrappers: list[exp.Subquery] = []
|
|
1996
|
+
inner = left
|
|
1997
|
+
while isinstance(inner, exp.Subquery) and inner.is_wrapper:
|
|
1998
|
+
wrappers.append(inner)
|
|
1999
|
+
inner = inner.this
|
|
2000
|
+
|
|
2001
|
+
if (
|
|
2002
|
+
isinstance(inner, exp.SetOperation)
|
|
2003
|
+
and not inner.args.get("with_")
|
|
2004
|
+
and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS)
|
|
2005
|
+
):
|
|
2006
|
+
for wrapper in wrappers:
|
|
2007
|
+
inner.add_comments(wrapper.pop_comments())
|
|
2008
|
+
left = inner
|
|
2009
|
+
elif (
|
|
2010
|
+
self.SET_OP_PARENTHESIZED_OPERANDS
|
|
2011
|
+
and isinstance(left, exp.Subquery)
|
|
2012
|
+
and left.is_wrapper
|
|
2013
|
+
and not left.comments
|
|
2014
|
+
and isinstance(left.this, exp.SetOperation)
|
|
2015
|
+
and not left.this.args.get("with_")
|
|
2016
|
+
and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS)
|
|
2017
|
+
):
|
|
2018
|
+
# Write a plain grouping's parentheses here rather than recursing through
|
|
2019
|
+
# subquery_sql: left-to-right chains nest one grouping per INTERSECT, and
|
|
2020
|
+
# long ones would exhaust the stack. The depth pushed here marks the close.
|
|
2021
|
+
open_parens += self.indent("(", level=depth, pad=0) + self.sep("")
|
|
2022
|
+
stack.append(depth)
|
|
2023
|
+
depth += 1
|
|
2024
|
+
left = left.this
|
|
2025
|
+
stack.append(left)
|
|
1980
2026
|
else:
|
|
1981
2027
|
if (
|
|
1982
2028
|
not self.SET_OP_LIMITS
|
|
@@ -1986,7 +2032,14 @@ class Generator:
|
|
|
1986
2032
|
node = node.subquery(copy=False)
|
|
1987
2033
|
if not self.SET_OP_PARENTHESIZED_OPERANDS:
|
|
1988
2034
|
node = exp.select("*").from_(node, copy=False)
|
|
1989
|
-
|
|
2035
|
+
elif (
|
|
2036
|
+
not self.SET_OP_PARENTHESIZED_OPERANDS
|
|
2037
|
+
and isinstance(node, exp.Subquery)
|
|
2038
|
+
and isinstance(node.unnest(), exp.SetOperation)
|
|
2039
|
+
):
|
|
2040
|
+
node = exp.select("*").from_(node, copy=False)
|
|
2041
|
+
sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0))
|
|
2042
|
+
open_parens = ""
|
|
1990
2043
|
|
|
1991
2044
|
this = self.sep().join(sqls)
|
|
1992
2045
|
this = self.query_modifiers(expression, this)
|
|
@@ -2014,6 +2067,9 @@ class Generator:
|
|
|
2014
2067
|
where = self.sql(expression, "expression").strip()
|
|
2015
2068
|
return f"{this} FILTER({where})"
|
|
2016
2069
|
|
|
2070
|
+
def per_sql(self, expression: exp.Per) -> str:
|
|
2071
|
+
return f"{self.sql(expression, 'this')} PER ({self.expressions(expression, flat=True)})"
|
|
2072
|
+
|
|
2017
2073
|
def hint_sql(self, expression: exp.Hint) -> str:
|
|
2018
2074
|
if not self.QUERY_HINTS:
|
|
2019
2075
|
self.unsupported("Hints are not supported")
|
|
@@ -1978,6 +1978,42 @@ class DuckDBGenerator(generator.Generator):
|
|
|
1978
1978
|
"show",
|
|
1979
1979
|
"summarize",
|
|
1980
1980
|
"unpivot",
|
|
1981
|
+
"anti",
|
|
1982
|
+
"asof",
|
|
1983
|
+
"at",
|
|
1984
|
+
"authorization",
|
|
1985
|
+
"binary",
|
|
1986
|
+
"by",
|
|
1987
|
+
"collation",
|
|
1988
|
+
"columns",
|
|
1989
|
+
"concurrently",
|
|
1990
|
+
"cross",
|
|
1991
|
+
"freeze",
|
|
1992
|
+
"full",
|
|
1993
|
+
"generated",
|
|
1994
|
+
"glob",
|
|
1995
|
+
"ilike",
|
|
1996
|
+
"inner",
|
|
1997
|
+
"is",
|
|
1998
|
+
"isnull",
|
|
1999
|
+
"join",
|
|
2000
|
+
"left",
|
|
2001
|
+
"like",
|
|
2002
|
+
"map",
|
|
2003
|
+
"natural",
|
|
2004
|
+
"notnull",
|
|
2005
|
+
"outer",
|
|
2006
|
+
"overlaps",
|
|
2007
|
+
"positional",
|
|
2008
|
+
"right",
|
|
2009
|
+
"semi",
|
|
2010
|
+
"similar",
|
|
2011
|
+
"struct",
|
|
2012
|
+
"tablesample",
|
|
2013
|
+
"try_cast",
|
|
2014
|
+
"tuple",
|
|
2015
|
+
"unpack",
|
|
2016
|
+
"verbose",
|
|
1981
2017
|
}
|
|
1982
2018
|
|
|
1983
2019
|
UNWRAPPED_INTERVAL_VALUES = (exp.Literal, exp.Paren)
|
|
@@ -4568,10 +4604,8 @@ class DuckDBGenerator(generator.Generator):
|
|
|
4568
4604
|
else:
|
|
4569
4605
|
result = self.func("DATE_TRUNC", unit, date)
|
|
4570
4606
|
|
|
4571
|
-
if (
|
|
4572
|
-
|
|
4573
|
-
and date.is_type(*exp.DataType.TEMPORAL_TYPES)
|
|
4574
|
-
and not (is_date_unit(unit) and date.is_type(exp.DType.DATE))
|
|
4607
|
+
if expression.args.get("input_type_preserved") and date.is_type(
|
|
4608
|
+
*exp.DataType.TEMPORAL_TYPES
|
|
4575
4609
|
):
|
|
4576
4610
|
return self.sql(exp.Cast(this=result, to=date.type))
|
|
4577
4611
|
|
|
@@ -4628,9 +4662,7 @@ class DuckDBGenerator(generator.Generator):
|
|
|
4628
4662
|
result = self.func("DATE_TRUNC", unit, date_time)
|
|
4629
4663
|
return self.sql(exp.Cast(this=result, to=timestamp.type))
|
|
4630
4664
|
|
|
4631
|
-
if timestamp.is_type(*exp.DataType.TEMPORAL_TYPES)
|
|
4632
|
-
date_unit and timestamp.is_type(exp.DType.DATE)
|
|
4633
|
-
):
|
|
4665
|
+
if timestamp.is_type(*exp.DataType.TEMPORAL_TYPES):
|
|
4634
4666
|
return self.sql(exp.Cast(this=result, to=timestamp.type))
|
|
4635
4667
|
|
|
4636
4668
|
return result
|
|
@@ -12,6 +12,7 @@ from sqlglot.dialects.dialect import (
|
|
|
12
12
|
array_concat_sql,
|
|
13
13
|
bool_xor_sql,
|
|
14
14
|
count_if_to_sum,
|
|
15
|
+
date_delta_to_binary_sql,
|
|
15
16
|
datestrtodate_sql,
|
|
16
17
|
filter_array_using_unnest,
|
|
17
18
|
generate_series_sql,
|
|
@@ -52,28 +53,39 @@ DATE_DIFF_FACTOR = {
|
|
|
52
53
|
"DAY": " / 86400",
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
DATE_DIFF_BOUNDARY_UNITS = {
|
|
57
|
+
"MICROSECOND",
|
|
58
|
+
"MILLISECOND",
|
|
59
|
+
"SECOND",
|
|
60
|
+
"MINUTE",
|
|
61
|
+
"HOUR",
|
|
62
|
+
"MONTH",
|
|
63
|
+
"QUARTER",
|
|
64
|
+
"YEAR",
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
|
|
56
68
|
def _date_add_sql(kind: str) -> t.Callable[[PostgresGenerator, DATE_ADD_OR_SUB], str]:
|
|
57
69
|
def func(self: PostgresGenerator, expression: DATE_ADD_OR_SUB) -> str:
|
|
58
70
|
if isinstance(expression, exp.TsOrDsAdd):
|
|
59
71
|
expression = ts_or_ds_add_cast(expression)
|
|
60
72
|
|
|
61
|
-
this =
|
|
73
|
+
this = expression.this
|
|
62
74
|
unit = expression.args.get("unit")
|
|
63
75
|
|
|
64
76
|
e = self._simplify_unless_literal(expression.expression)
|
|
77
|
+
interval: exp.Expr
|
|
65
78
|
if isinstance(e, exp.Interval):
|
|
66
|
-
|
|
67
|
-
elif
|
|
68
|
-
e.
|
|
69
|
-
elif e.is_number:
|
|
70
|
-
e = exp.Literal.string(e.to_py())
|
|
79
|
+
interval = e
|
|
80
|
+
elif e.is_number or e.is_string:
|
|
81
|
+
interval = exp.Interval(this=exp.Literal.string(e.to_py()), unit=unit)
|
|
71
82
|
else:
|
|
72
83
|
one = exp.Literal.number(1)
|
|
73
|
-
|
|
74
|
-
return f"{this} {kind} {self.sql(interval_times_value)}"
|
|
84
|
+
interval = exp.Interval(this=one, unit=unit) * e
|
|
75
85
|
|
|
76
|
-
return
|
|
86
|
+
return date_delta_to_binary_sql(
|
|
87
|
+
self, expression, this + interval if kind == "+" else this - interval
|
|
88
|
+
)
|
|
77
89
|
|
|
78
90
|
return func
|
|
79
91
|
|
|
@@ -104,6 +116,13 @@ def _date_diff_sql(self: PostgresGenerator, expression: exp.DateDiff | exp.TsOrD
|
|
|
104
116
|
end = f"CAST({self.sql(expression, 'this')} AS TIMESTAMP)"
|
|
105
117
|
start = f"CAST({self.sql(expression, 'expression')} AS TIMESTAMP)"
|
|
106
118
|
|
|
119
|
+
# The arithmetic below counts complete units between the operands, whereas dialects that set
|
|
120
|
+
# date_part_boundary count unit boundary crossings, so the operands are truncated down to the
|
|
121
|
+
# unit to make the two coincide. WEEK is left out: its start day differs across dialects
|
|
122
|
+
if expression.args.get("date_part_boundary") and unit in DATE_DIFF_BOUNDARY_UNITS:
|
|
123
|
+
end = self.func("DATE_TRUNC", exp.Literal.string(unit), end)
|
|
124
|
+
start = self.func("DATE_TRUNC", exp.Literal.string(unit), start)
|
|
125
|
+
|
|
107
126
|
if factor is not None:
|
|
108
127
|
return f"CAST(EXTRACT(epoch FROM {end} - {start}){factor} AS BIGINT)"
|
|
109
128
|
|
|
@@ -336,6 +355,8 @@ class PostgresGenerator(generator.Generator):
|
|
|
336
355
|
exp.DateDiff: _date_diff_sql,
|
|
337
356
|
exp.DateStrToDate: datestrtodate_sql,
|
|
338
357
|
exp.DateSub: _date_add_sql("-"),
|
|
358
|
+
exp.DatetimeAdd: _date_add_sql("+"),
|
|
359
|
+
exp.DatetimeSub: _date_add_sql("-"),
|
|
339
360
|
exp.Day: _day_month_year_sql,
|
|
340
361
|
exp.Explode: rename_func("UNNEST"),
|
|
341
362
|
exp.ExplodingGenerateSeries: rename_func("GENERATE_SERIES"),
|
|
@@ -400,6 +421,8 @@ class PostgresGenerator(generator.Generator):
|
|
|
400
421
|
exp.TimeFromParts: rename_func("MAKE_TIME"),
|
|
401
422
|
exp.TimestampFromParts: rename_func("MAKE_TIMESTAMP"),
|
|
402
423
|
exp.TimestampTrunc: timestamptrunc_sql(zone=True),
|
|
424
|
+
exp.TimestampAdd: _date_add_sql("+"),
|
|
425
|
+
exp.TimestampSub: _date_add_sql("-"),
|
|
403
426
|
exp.TimeStrToTime: timestrtotime_sql,
|
|
404
427
|
exp.TimeToStr: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)),
|
|
405
428
|
exp.ToChar: lambda self, e: (
|
|
@@ -78,6 +78,7 @@ def qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr:
|
|
|
78
78
|
isinstance(expression, (exp.CTE, exp.Subquery))
|
|
79
79
|
and isinstance(alias, exp.TableAlias)
|
|
80
80
|
and not alias.columns
|
|
81
|
+
and isinstance(expression.this, exp.Query)
|
|
81
82
|
):
|
|
82
83
|
from sqlglot.dialects.tsql import TSQL
|
|
83
84
|
from sqlglot.optimizer.qualify_columns import qualify_outputs
|
|
@@ -217,12 +217,9 @@ class TypeAnnotator:
|
|
|
217
217
|
# Databricks and Spark ≥v3 actually support NULL (i.e., VOID) as a type
|
|
218
218
|
self._supports_null_type = dialect.SUPPORTS_NULL_TYPE
|
|
219
219
|
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
|
|
223
|
-
self._setop_column_types: dict[
|
|
224
|
-
int, tuple[exp.SetOperation, dict[str, exp.DataType | exp.DType]]
|
|
225
|
-
] = {}
|
|
220
|
+
# Keep ordered output columns until a parent set operation consumes them. Source
|
|
221
|
+
# queries retain their results for subsequent column lookups.
|
|
222
|
+
self._setop_column_types: dict[int, list[tuple[str, exp.DataType | exp.DType]]] = {}
|
|
226
223
|
self._table_column_aliases: dict[int, tuple[exp.Table, dict[str, str]]] = {}
|
|
227
224
|
|
|
228
225
|
# When set to False, this enables partial annotation by skipping already-annotated nodes
|
|
@@ -346,6 +343,16 @@ class TypeAnnotator:
|
|
|
346
343
|
|
|
347
344
|
def _get_source_scope_selects(self, source: Scope) -> dict[str, exp.DataType | exp.DType]:
|
|
348
345
|
expression = source.expression
|
|
346
|
+
|
|
347
|
+
if source.is_cte:
|
|
348
|
+
# Recursive references can point to an unfinished UNION. Use its nearest resolved
|
|
349
|
+
# anchor prefix until the recursive branches have been annotated.
|
|
350
|
+
while (
|
|
351
|
+
isinstance(expression, exp.SetOperation)
|
|
352
|
+
and id(expression) not in self._setop_column_types
|
|
353
|
+
):
|
|
354
|
+
expression = expression.this.unnest()
|
|
355
|
+
|
|
349
356
|
if isinstance(expression, exp.UDTF):
|
|
350
357
|
values = []
|
|
351
358
|
|
|
@@ -391,11 +398,8 @@ class TypeAnnotator:
|
|
|
391
398
|
|
|
392
399
|
return {alias: column.type for alias, column in zip(alias_column_names, values)}
|
|
393
400
|
|
|
394
|
-
if isinstance(expression, exp.SetOperation)
|
|
395
|
-
|
|
396
|
-
or len(expression.this.selects) == len(expression.expression.selects)
|
|
397
|
-
):
|
|
398
|
-
return self._get_setop_column_types(expression)
|
|
401
|
+
if isinstance(expression, exp.SetOperation):
|
|
402
|
+
return dict(self._get_setop_column_types(expression))
|
|
399
403
|
|
|
400
404
|
if isinstance(expression, exp.Selectable):
|
|
401
405
|
return {s.alias_or_name: s.type for s in expression.selects if s.type}
|
|
@@ -410,7 +414,7 @@ class TypeAnnotator:
|
|
|
410
414
|
if self.dialect.QUERY_RESULTS_ARE_STRUCTS and isinstance(scope.expression, exp.Query):
|
|
411
415
|
scope.expression.meta.pop("query_type", None)
|
|
412
416
|
column_types = (
|
|
413
|
-
self._get_setop_column_types(scope.expression)
|
|
417
|
+
self._get_setop_column_types(scope.expression)
|
|
414
418
|
if isinstance(scope.expression, exp.SetOperation)
|
|
415
419
|
else ((select.output_name, select.type) for select in scope.expression.selects)
|
|
416
420
|
)
|
|
@@ -426,7 +430,7 @@ class TypeAnnotator:
|
|
|
426
430
|
nested=True,
|
|
427
431
|
)
|
|
428
432
|
|
|
429
|
-
if not any(
|
|
433
|
+
if struct_type.expressions and not any(
|
|
430
434
|
not cd.kind or cd.kind.is_type(exp.DType.UNKNOWN) for cd in struct_type.expressions
|
|
431
435
|
):
|
|
432
436
|
# We don't use `_set_type` on purpose here. If we annotated the query directly, then
|
|
@@ -527,6 +531,9 @@ class TypeAnnotator:
|
|
|
527
531
|
expr.meta["nonnull"] = True
|
|
528
532
|
continue
|
|
529
533
|
|
|
534
|
+
if isinstance(expr, exp.SetOperation):
|
|
535
|
+
self._annotate_set_operation(expr)
|
|
536
|
+
|
|
530
537
|
spec = self.expression_metadata.get(expr.__class__)
|
|
531
538
|
|
|
532
539
|
if spec and (annotator := spec.get("annotator")):
|
|
@@ -682,75 +689,55 @@ class TypeAnnotator:
|
|
|
682
689
|
return type2_value if type2_value in self.coerces_to.get(type1_value, {}) else type1_value
|
|
683
690
|
|
|
684
691
|
def _get_setop_column_types(
|
|
685
|
-
self,
|
|
686
|
-
) ->
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
Returns:
|
|
697
|
-
Dictionary mapping column names to their coerced types
|
|
698
|
-
"""
|
|
699
|
-
setop_id = id(setop)
|
|
700
|
-
if cached := self._setop_column_types.get(setop_id):
|
|
701
|
-
return cached[1]
|
|
702
|
-
|
|
703
|
-
col_types: dict[str, exp.DataType | exp.DType] = {}
|
|
704
|
-
|
|
705
|
-
# Validate that left and right have same number of projections (BY NAME
|
|
706
|
-
# operations match columns by name, so their counts are allowed to differ)
|
|
707
|
-
if not (
|
|
708
|
-
isinstance(setop, exp.SetOperation)
|
|
709
|
-
and setop.this.selects
|
|
710
|
-
and setop.expression.selects
|
|
711
|
-
and (
|
|
712
|
-
setop.args.get("by_name")
|
|
713
|
-
or len(setop.this.selects) == len(setop.expression.selects)
|
|
714
|
-
)
|
|
715
|
-
):
|
|
716
|
-
return col_types
|
|
717
|
-
|
|
718
|
-
# Process a chain / sub-tree of set operations
|
|
719
|
-
for set_op in setop.walk(
|
|
720
|
-
prune=lambda n: not isinstance(n, (exp.SetOperation, exp.Subquery))
|
|
721
|
-
):
|
|
722
|
-
if not isinstance(set_op, exp.SetOperation):
|
|
723
|
-
continue
|
|
724
|
-
|
|
725
|
-
if set_op.args.get("by_name"):
|
|
726
|
-
# Columns missing from one side are filled with NULLs, so the other
|
|
727
|
-
# side's type is preserved (NULL is the identity for _maybe_coerce)
|
|
728
|
-
r_type_by_select = {s.alias_or_name: s.type for s in set_op.expression.selects}
|
|
729
|
-
setop_cols = {
|
|
730
|
-
s.alias_or_name: self._maybe_coerce(
|
|
731
|
-
s.type or exp.DType.UNKNOWN,
|
|
732
|
-
r_type_by_select.pop(s.alias_or_name, exp.DType.NULL) or exp.DType.UNKNOWN,
|
|
733
|
-
)
|
|
734
|
-
for s in set_op.this.selects
|
|
735
|
-
}
|
|
736
|
-
for name, r_type in r_type_by_select.items():
|
|
737
|
-
setop_cols[name] = r_type or exp.DType.UNKNOWN
|
|
692
|
+
self, expression: exp.SetOperation
|
|
693
|
+
) -> list[tuple[str, exp.DataType | exp.DType]]:
|
|
694
|
+
return self._setop_column_types.get(id(expression), [])
|
|
695
|
+
|
|
696
|
+
def _annotate_set_operation(self, expression: exp.SetOperation) -> None:
|
|
697
|
+
# The expression annotation pass visits children first, including when a scope is
|
|
698
|
+
# annotated independently. Preserve duplicate names until positional matching is done.
|
|
699
|
+
operands: list[list[tuple[str, exp.DataType | exp.DType]]] = []
|
|
700
|
+
for operand in (expression.this.unnest(), expression.expression.unnest()):
|
|
701
|
+
if isinstance(operand, exp.SetOperation):
|
|
702
|
+
operands.append(self._setop_column_types.pop(id(operand), []))
|
|
738
703
|
else:
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
ls.type or exp.DType.UNKNOWN, rs.type or exp.DType.UNKNOWN
|
|
742
|
-
)
|
|
743
|
-
for ls, rs in zip(set_op.this.selects, set_op.expression.selects)
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
# Coerce intermediate results with the previously registered types, if they exist
|
|
747
|
-
for col_name, col_type in setop_cols.items():
|
|
748
|
-
col_types[col_name] = self._maybe_coerce(
|
|
749
|
-
col_type, col_types.get(col_name, exp.DType.NULL)
|
|
704
|
+
operands.append(
|
|
705
|
+
[(s.alias_or_name, s.type or exp.DType.UNKNOWN) for s in operand.selects]
|
|
750
706
|
)
|
|
707
|
+
left, right = operands
|
|
708
|
+
|
|
709
|
+
columns: list[tuple[str, exp.DataType | exp.DType]] = []
|
|
710
|
+
if left and right:
|
|
711
|
+
if expression.args.get("by_name"):
|
|
712
|
+
left_types = dict(left)
|
|
713
|
+
right_types = dict(right)
|
|
714
|
+
if on := expression.args.get("on"):
|
|
715
|
+
names = [column.name for column in on]
|
|
716
|
+
elif expression.kind == "INNER":
|
|
717
|
+
names = [name for name in left_types if name in right_types]
|
|
718
|
+
elif expression.side == "LEFT":
|
|
719
|
+
names = list(left_types)
|
|
720
|
+
else:
|
|
721
|
+
names = list({**left_types, **right_types})
|
|
722
|
+
|
|
723
|
+
# Missing columns are NULL-filled, preserving the other operand's type.
|
|
724
|
+
columns = [
|
|
725
|
+
(
|
|
726
|
+
name,
|
|
727
|
+
self._maybe_coerce(
|
|
728
|
+
left_types.get(name, exp.DType.NULL),
|
|
729
|
+
right_types.get(name, exp.DType.NULL),
|
|
730
|
+
),
|
|
731
|
+
)
|
|
732
|
+
for name in names
|
|
733
|
+
]
|
|
734
|
+
elif len(left) == len(right):
|
|
735
|
+
columns = [
|
|
736
|
+
(name, self._maybe_coerce(left_type, right_type))
|
|
737
|
+
for (name, left_type), (_, right_type) in zip(left, right)
|
|
738
|
+
]
|
|
751
739
|
|
|
752
|
-
self._setop_column_types[
|
|
753
|
-
return col_types
|
|
740
|
+
self._setop_column_types[id(expression)] = columns
|
|
754
741
|
|
|
755
742
|
def _get_unpivot_column_types(
|
|
756
743
|
self, pivot: exp.Pivot, src_types: dict[str, exp.DataType | exp.DType]
|
|
@@ -911,9 +898,18 @@ class TypeAnnotator:
|
|
|
911
898
|
return expression
|
|
912
899
|
|
|
913
900
|
if nested_type:
|
|
901
|
+
# Only resolve ARRAY<NULL>; preserve existing coercion for other nested types.
|
|
902
|
+
if (
|
|
903
|
+
nested_type.is_type(exp.DType.ARRAY)
|
|
904
|
+
and nested_type.expressions
|
|
905
|
+
and nested_type.expressions[0].is_type(exp.DType.NULL)
|
|
906
|
+
and expr_type.is_type(exp.DType.ARRAY)
|
|
907
|
+
and expr_type.expressions
|
|
908
|
+
):
|
|
909
|
+
nested_type = expr_type
|
|
914
910
|
continue
|
|
915
911
|
|
|
916
|
-
#
|
|
912
|
+
# Prefer the first nested data type, resolving ARRAY<NULL> from later arrays.
|
|
917
913
|
if expr_type.args.get("nested"):
|
|
918
914
|
nested_type = expr_type
|
|
919
915
|
elif isinstance(expr, exp.Literal):
|
|
@@ -947,7 +943,9 @@ class TypeAnnotator:
|
|
|
947
943
|
if result_type is None:
|
|
948
944
|
result_type = self._maybe_coerce(non_literal_type, literal_type)
|
|
949
945
|
else:
|
|
950
|
-
result_type =
|
|
946
|
+
result_type = (
|
|
947
|
+
literal_type or non_literal_type or (exp.DType.NULL if array else exp.DType.UNKNOWN)
|
|
948
|
+
)
|
|
951
949
|
|
|
952
950
|
self._set_type(expression, result_type)
|
|
953
951
|
|
|
@@ -958,7 +956,7 @@ class TypeAnnotator:
|
|
|
958
956
|
elif this_type in exp.DataType.FLOAT_TYPES:
|
|
959
957
|
self._set_type(expression, exp.DType.DOUBLE)
|
|
960
958
|
|
|
961
|
-
if array:
|
|
959
|
+
if array and not expression.is_type(exp.DType.UNKNOWN):
|
|
962
960
|
self._set_type(
|
|
963
961
|
expression,
|
|
964
962
|
exp.DataType(this=exp.DType.ARRAY, expressions=[expression.type], nested=True),
|
|
@@ -1121,12 +1119,20 @@ class TypeAnnotator:
|
|
|
1121
1119
|
|
|
1122
1120
|
map_type = exp.DataType(this=exp.DType.MAP)
|
|
1123
1121
|
if isinstance(keys, exp.Array) and isinstance(values, exp.Array):
|
|
1124
|
-
key_type = seq_get(keys.type.expressions, 0)
|
|
1125
|
-
value_type = seq_get(values.type.expressions, 0)
|
|
1122
|
+
key_type = seq_get(keys.type.expressions, 0)
|
|
1123
|
+
value_type = seq_get(values.type.expressions, 0)
|
|
1124
|
+
|
|
1125
|
+
if (
|
|
1126
|
+
not key_type
|
|
1127
|
+
or key_type.is_type(exp.DType.UNKNOWN)
|
|
1128
|
+
or not value_type
|
|
1129
|
+
or value_type.is_type(exp.DType.UNKNOWN)
|
|
1130
|
+
):
|
|
1131
|
+
self._set_type(expression, exp.DType.UNKNOWN)
|
|
1132
|
+
return expression
|
|
1126
1133
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
map_type.set("nested", True)
|
|
1134
|
+
map_type.set("expressions", [key_type, value_type])
|
|
1135
|
+
map_type.set("nested", True)
|
|
1130
1136
|
|
|
1131
1137
|
self._set_type(expression, map_type)
|
|
1132
1138
|
return expression
|
|
@@ -361,7 +361,9 @@ class Resolver:
|
|
|
361
361
|
available_sources = {from_name: self.get_source_columns(from_name)}
|
|
362
362
|
|
|
363
363
|
for join in args["joins"][: t.cast(int, join_ancestor.index) + 1]:
|
|
364
|
-
|
|
364
|
+
# An earlier SEMI/ANTI join's table is only visible within its own ON clause
|
|
365
|
+
if join is join_ancestor or not join.is_semi_or_anti_join:
|
|
366
|
+
available_sources[join.alias_or_name] = self.get_source_columns(join.alias_or_name)
|
|
365
367
|
|
|
366
368
|
return available_sources
|
|
367
369
|
|
|
@@ -819,11 +819,16 @@ def _traverse_set_operation(scope: Scope) -> Iterator[Scope]:
|
|
|
819
819
|
scope_type=ScopeType.SET_OPERATION,
|
|
820
820
|
)
|
|
821
821
|
|
|
822
|
-
|
|
822
|
+
# Use the scope's unwrapped expression rather than the operand itself so parenthesized
|
|
823
|
+
# left operands are descended into iteratively. Only descend while prev_scope is None,
|
|
824
|
+
# i.e. until the chain's leftmost query has been scoped; later scopes are paired as
|
|
825
|
+
# right operands.
|
|
826
|
+
inner = new_scope.expression
|
|
827
|
+
if prev_scope is None and isinstance(inner, exp.SetOperation):
|
|
823
828
|
yield from _traverse_ctes(new_scope)
|
|
824
829
|
|
|
825
830
|
set_op_scope_stack.append(new_scope)
|
|
826
|
-
expression_stack.extend([
|
|
831
|
+
expression_stack.extend([inner.expression, inner.this])
|
|
827
832
|
continue
|
|
828
833
|
|
|
829
834
|
branch_scope: Scope | None = None
|