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.
Files changed (118) hide show
  1. {sqlglotc-30.25.0/sqlglotc.egg-info → sqlglotc-30.26.0}/PKG-INFO +3 -3
  2. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/pyproject.toml +2 -2
  3. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/core.py +4 -0
  4. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/properties.py +4 -0
  5. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generator.py +60 -4
  6. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/duckdb.py +39 -7
  7. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/postgres.py +32 -9
  8. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/tsql.py +1 -0
  9. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/annotate_types.py +93 -87
  10. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/resolver.py +3 -1
  11. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/scope.py +7 -2
  12. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parser.py +61 -15
  13. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/clickhouse.py +1 -4
  14. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/duckdb.py +14 -1
  15. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/mysql.py +8 -0
  16. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/oracle.py +5 -0
  17. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/postgres.py +23 -0
  18. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/snowflake.py +37 -16
  19. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/spark.py +1 -1
  20. {sqlglotc-30.25.0 → sqlglotc-30.26.0/sqlglotc.egg-info}/PKG-INFO +3 -3
  21. sqlglotc-30.26.0/sqlglotc.egg-info/requires.txt +6 -0
  22. sqlglotc-30.26.0/sqlglotc.egg-info/scm_version.json +8 -0
  23. sqlglotc-30.25.0/sqlglotc.egg-info/requires.txt +0 -6
  24. sqlglotc-30.25.0/sqlglotc.egg-info/scm_version.json +0 -8
  25. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/MANIFEST.in +0 -0
  26. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/setup.cfg +0 -0
  27. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/setup.py +0 -0
  28. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/anonymize.py +0 -0
  29. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/errors.py +0 -0
  30. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/executor/table.py +0 -0
  31. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/aggregate.py +0 -0
  32. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/array.py +0 -0
  33. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/builders.py +0 -0
  34. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/constraints.py +0 -0
  35. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/datatypes.py +0 -0
  36. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/ddl.py +0 -0
  37. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/dml.py +0 -0
  38. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/functions.py +0 -0
  39. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/json.py +0 -0
  40. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/math.py +0 -0
  41. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/query.py +0 -0
  42. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/string.py +0 -0
  43. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/expressions/temporal.py +0 -0
  44. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/athena.py +0 -0
  45. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/bigquery.py +0 -0
  46. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/clickhouse.py +0 -0
  47. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/databricks.py +0 -0
  48. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/dax.py +0 -0
  49. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/doris.py +0 -0
  50. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/dremio.py +0 -0
  51. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/drill.py +0 -0
  52. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/druid.py +0 -0
  53. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/dune.py +0 -0
  54. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/exasol.py +0 -0
  55. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/fabric.py +0 -0
  56. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/hive.py +0 -0
  57. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/materialize.py +0 -0
  58. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/mysql.py +0 -0
  59. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/oracle.py +0 -0
  60. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/presto.py +0 -0
  61. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/prql.py +0 -0
  62. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/python.py +0 -0
  63. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/redshift.py +0 -0
  64. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/risingwave.py +0 -0
  65. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/singlestore.py +0 -0
  66. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/snowflake.py +0 -0
  67. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/solr.py +0 -0
  68. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/spark.py +0 -0
  69. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/spark2.py +0 -0
  70. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/sqlite.py +0 -0
  71. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/starrocks.py +0 -0
  72. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/tableau.py +0 -0
  73. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/teradata.py +0 -0
  74. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/generators/trino.py +0 -0
  75. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/helper.py +0 -0
  76. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/lineage.py +0 -0
  77. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  78. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  79. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  80. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/qualify.py +0 -0
  81. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/qualify_columns.py +0 -0
  82. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/qualify_tables.py +0 -0
  83. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/optimizer/simplify.py +0 -0
  84. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/athena.py +0 -0
  85. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/base.py +0 -0
  86. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/bigquery.py +0 -0
  87. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/databricks.py +0 -0
  88. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/dax.py +0 -0
  89. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/doris.py +0 -0
  90. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/dremio.py +0 -0
  91. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/drill.py +0 -0
  92. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/druid.py +0 -0
  93. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/dune.py +0 -0
  94. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/exasol.py +0 -0
  95. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/fabric.py +0 -0
  96. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/hive.py +0 -0
  97. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/materialize.py +0 -0
  98. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/presto.py +0 -0
  99. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/prql.py +0 -0
  100. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/redshift.py +0 -0
  101. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/risingwave.py +0 -0
  102. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/singlestore.py +0 -0
  103. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/solr.py +0 -0
  104. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/spark2.py +0 -0
  105. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/sqlite.py +0 -0
  106. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/starrocks.py +0 -0
  107. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/tableau.py +0 -0
  108. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/teradata.py +0 -0
  109. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/trino.py +0 -0
  110. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/parsers/tsql.py +0 -0
  111. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/serde.py +0 -0
  112. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/time.py +0 -0
  113. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/tokenizer_core.py +0 -0
  114. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglot/trie.py +0 -0
  115. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  116. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  117. {sqlglotc-30.25.0 → sqlglotc-30.26.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  118. {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.25.0
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.25.0
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",
@@ -2108,6 +2108,10 @@ class Filter(Expression):
2108
2108
  arg_types = {"this": True, "expression": True}
2109
2109
 
2110
2110
 
2111
+ class Per(Expression):
2112
+ arg_types = {"this": True, "expressions": True}
2113
+
2114
+
2111
2115
  class Check(Expression):
2112
2116
  pass
2113
2117
 
@@ -107,6 +107,10 @@ class CopyGrantsProperty(Property):
107
107
  arg_types = {}
108
108
 
109
109
 
110
+ class CopyTagsProperty(Property):
111
+ arg_types = {}
112
+
113
+
110
114
  class DataBlocksizeProperty(Property):
111
115
  arg_types = {
112
116
  "size": False,
@@ -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, exp.SetOperation):
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
- stack.append(node.this)
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
- sqls.append(self.sql(node))
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
- expression.args.get("input_type_preserved")
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) and not (
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 = self.sql(expression, "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
- return f"{this} {kind} {self.sql(e)}"
67
- elif isinstance(e, exp.Literal):
68
- e.set("is_string", True)
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
- interval_times_value = exp.Interval(this=one, unit=unit) * e
74
- return f"{this} {kind} {self.sql(interval_times_value)}"
84
+ interval = exp.Interval(this=one, unit=unit) * e
75
85
 
76
- return f"{this} {kind} {self.sql(exp.Interval(this=e, unit=unit))}"
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
- # Maps a SetOperation's id to itself and its projection types, keeping the node alive.
221
- # Selecting from the same source multiple times would otherwise reprocess its subtree
222
- # to coerce the types of its operands' projections
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) and (
395
- expression.args.get("by_name")
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).items()
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, setop: exp.SetOperation
686
- ) -> dict[str, exp.DataType | exp.DType]:
687
- """
688
- Computes and returns the coerced column types for a SetOperation.
689
-
690
- This handles UNION, INTERSECT, EXCEPT, etc., coercing types across
691
- left and right operands for all projections/columns.
692
-
693
- Args:
694
- setop: The SetOperation expression to analyze
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
- setop_cols = {
740
- ls.alias_or_name: self._maybe_coerce(
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[setop_id] = (setop, col_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
- # Stop coercing at the first nested data type found
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 = literal_type or non_literal_type or exp.DType.UNKNOWN
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) or exp.DType.UNKNOWN
1125
- value_type = seq_get(values.type.expressions, 0) or exp.DType.UNKNOWN
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
- if key_type != exp.DType.UNKNOWN and value_type != exp.DType.UNKNOWN:
1128
- map_type.set("expressions", [key_type, value_type])
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
- available_sources[join.alias_or_name] = self.get_source_columns(join.alias_or_name)
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
- if isinstance(expression, exp.SetOperation):
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([expression.expression, expression.this])
831
+ expression_stack.extend([inner.expression, inner.this])
827
832
  continue
828
833
 
829
834
  branch_scope: Scope | None = None