sqlglotc 30.10.0__tar.gz → 30.11.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 (112) hide show
  1. {sqlglotc-30.10.0/sqlglotc.egg-info → sqlglotc-30.11.0}/PKG-INFO +3 -1
  2. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/pyproject.toml +1 -1
  3. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/setup.py +33 -0
  4. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/builders.py +6 -1
  5. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/core.py +8 -0
  6. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generator.py +2 -2
  7. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/bigquery.py +3 -3
  8. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/databricks.py +10 -12
  9. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/tsql.py +9 -0
  10. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/lineage.py +124 -20
  11. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/annotate_types.py +4 -4
  12. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/normalize_identifiers.py +4 -3
  13. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/qualify_columns.py +23 -11
  14. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/resolver.py +6 -4
  15. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/scope.py +32 -8
  16. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/simplify.py +19 -12
  17. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/bigquery.py +4 -4
  18. {sqlglotc-30.10.0 → sqlglotc-30.11.0/sqlglotc.egg-info}/PKG-INFO +3 -1
  19. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglotc.egg-info/requires.txt +1 -0
  20. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/MANIFEST.in +0 -0
  21. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/setup.cfg +0 -0
  22. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/errors.py +0 -0
  23. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/executor/table.py +0 -0
  24. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/aggregate.py +0 -0
  25. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/array.py +0 -0
  26. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/constraints.py +0 -0
  27. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/datatypes.py +0 -0
  28. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/ddl.py +0 -0
  29. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/dml.py +0 -0
  30. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/functions.py +0 -0
  31. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/json.py +0 -0
  32. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/math.py +0 -0
  33. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/properties.py +0 -0
  34. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/query.py +0 -0
  35. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/string.py +0 -0
  36. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/expressions/temporal.py +0 -0
  37. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/athena.py +0 -0
  38. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/clickhouse.py +0 -0
  39. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/doris.py +0 -0
  40. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/dremio.py +0 -0
  41. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/drill.py +0 -0
  42. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/druid.py +0 -0
  43. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/duckdb.py +0 -0
  44. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/dune.py +0 -0
  45. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/exasol.py +0 -0
  46. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/fabric.py +0 -0
  47. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/hive.py +0 -0
  48. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/materialize.py +0 -0
  49. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/mysql.py +0 -0
  50. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/oracle.py +0 -0
  51. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/postgres.py +0 -0
  52. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/presto.py +0 -0
  53. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/prql.py +0 -0
  54. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/python.py +0 -0
  55. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/redshift.py +0 -0
  56. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/risingwave.py +0 -0
  57. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/singlestore.py +0 -0
  58. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/snowflake.py +0 -0
  59. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/solr.py +0 -0
  60. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/spark.py +0 -0
  61. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/spark2.py +0 -0
  62. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/sqlite.py +0 -0
  63. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/starrocks.py +0 -0
  64. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/tableau.py +0 -0
  65. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/teradata.py +0 -0
  66. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/generators/trino.py +0 -0
  67. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/helper.py +0 -0
  68. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  69. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  70. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/qualify.py +0 -0
  71. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/optimizer/qualify_tables.py +0 -0
  72. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parser.py +0 -0
  73. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/athena.py +0 -0
  74. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/base.py +0 -0
  75. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/clickhouse.py +0 -0
  76. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/databricks.py +0 -0
  77. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/doris.py +0 -0
  78. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/dremio.py +0 -0
  79. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/drill.py +0 -0
  80. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/druid.py +0 -0
  81. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/duckdb.py +0 -0
  82. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/dune.py +0 -0
  83. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/exasol.py +0 -0
  84. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/fabric.py +0 -0
  85. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/hive.py +0 -0
  86. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/materialize.py +0 -0
  87. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/mysql.py +0 -0
  88. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/oracle.py +0 -0
  89. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/postgres.py +0 -0
  90. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/presto.py +0 -0
  91. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/prql.py +0 -0
  92. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/redshift.py +0 -0
  93. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/risingwave.py +0 -0
  94. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/singlestore.py +0 -0
  95. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/snowflake.py +0 -0
  96. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/solr.py +0 -0
  97. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/spark.py +0 -0
  98. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/spark2.py +0 -0
  99. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/sqlite.py +0 -0
  100. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/starrocks.py +0 -0
  101. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/tableau.py +0 -0
  102. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/teradata.py +0 -0
  103. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/trino.py +0 -0
  104. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/parsers/tsql.py +0 -0
  105. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/schema.py +0 -0
  106. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/serde.py +0 -0
  107. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/time.py +0 -0
  108. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/tokenizer_core.py +0 -0
  109. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglot/trie.py +0 -0
  110. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  111. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  112. {sqlglotc-30.10.0 → sqlglotc-30.11.0}/sqlglotc.egg-info/top_level.txt +0 -0
@@ -1,13 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.10.0
3
+ Version: 30.11.0
4
4
  Summary: mypyc-compiled extensions for sqlglot
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://sqlglot.com/
8
8
  Project-URL: Repository, https://github.com/tobymao/sqlglot
9
9
  Requires-Python: >=3.10
10
+ Requires-Dist: sqlglot==30.11.0
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
12
13
  Requires-Dist: setuptools_scm; extra == "dev"
13
14
  Requires-Dist: sqlglot-mypy>=2.1.0.post2; extra == "dev"
15
+ Dynamic: requires-dist
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlglotc"
3
- dynamic = ["version"]
3
+ dynamic = ["version", "dependencies"]
4
4
  description = "mypyc-compiled extensions for sqlglot"
5
5
  authors = [{ name = "Toby Mao", email = "toby.mao@gmail.com" }]
6
6
  license = "MIT"
@@ -128,9 +128,42 @@ class sdist(_sdist):
128
128
  shutil.rmtree(local_sqlglot, ignore_errors=True)
129
129
 
130
130
 
131
+ def _sqlglot_requirement():
132
+ """Pin sqlglot to the matching version so resolvers keep the two packages in lockstep."""
133
+ try:
134
+ pkg_info = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PKG-INFO")
135
+ if os.path.isfile(pkg_info):
136
+ # Building from an sdist: no git metadata; the released version is frozen in PKG-INFO.
137
+ with open(pkg_info, encoding="utf-8") as fd:
138
+ version = next(
139
+ (line.split(":", 1)[1].strip() for line in fd if line.startswith("Version:")),
140
+ "",
141
+ )
142
+ else:
143
+ from setuptools_scm import get_version
144
+
145
+ version = get_version(
146
+ root="..",
147
+ relative_to=__file__,
148
+ local_scheme="no-local-version",
149
+ fallback_version="0.0.0",
150
+ )
151
+ except Exception as e:
152
+ print(f"sqlglotc: failed to determine the sqlglot version to pin: {e}")
153
+ version = ""
154
+
155
+ # Dev and fallback (no git metadata) builds have no matching sqlglot release on PyPI;
156
+ # depend on sqlglot unpinned since the extensions can't be imported without it.
157
+ if version in ("", "0.0.0") or ".dev" in version:
158
+ return ["sqlglot"]
159
+
160
+ return [f"sqlglot=={version}"]
161
+
162
+
131
163
  setup(
132
164
  name="sqlglotc",
133
165
  packages=[],
166
+ install_requires=_sqlglot_requirement(),
134
167
  ext_modules=mypycify(
135
168
  _source_paths(), opt_level=os.environ.get("MYPYC_OPT", "2"), separate=True, verbose=True
136
169
  ),
@@ -319,6 +319,11 @@ def parse_identifier(name: str | Identifier, dialect: DialectType = None) -> Ide
319
319
  Returns:
320
320
  The identifier ast node.
321
321
  """
322
+ if isinstance(name, str) and SAFE_IDENTIFIER_RE.match(name):
323
+ # Simple names parse to a single unquoted identifier in all dialects, so we can
324
+ # avoid the tokenizer/parser round-trip for them.
325
+ return Identifier(this=name, quoted=False)
326
+
322
327
  try:
323
328
  expression = maybe_parse(name, dialect=dialect, into=Identifier)
324
329
  except (ParseError, TokenError):
@@ -822,7 +827,7 @@ def replace_tables(
822
827
  mapping = {normalize_table_name(k, dialect=dialect): v for k, v in mapping.items()}
823
828
 
824
829
  def _replace_tables(node: Expr) -> Expr:
825
- if isinstance(node, Table) and node.meta.get("replace") is not False:
830
+ if isinstance(node, Table) and node.meta_get("replace") is not False:
826
831
  original = normalize_table_name(node, dialect=dialect)
827
832
  new_name = mapping.get(original)
828
833
 
@@ -240,6 +240,9 @@ class Expr:
240
240
  def meta(self) -> dict[str, t.Any]:
241
241
  raise NotImplementedError
242
242
 
243
+ def meta_get(self, key: str, default: t.Any = None) -> t.Any:
244
+ raise NotImplementedError
245
+
243
246
  def __deepcopy__(self, memo: t.Any) -> Expr:
244
247
  raise NotImplementedError
245
248
 
@@ -990,6 +993,11 @@ class Expression(Expr):
990
993
  self._meta = {}
991
994
  return self._meta
992
995
 
996
+ def meta_get(self, key: str, default: t.Any = None) -> t.Any:
997
+ """Reads a meta value without allocating the meta dict (unlike the `meta` property)."""
998
+ meta = self._meta
999
+ return meta.get(key, default) if meta is not None else default
1000
+
993
1001
  def __deepcopy__(self, memo: t.Any) -> Expr:
994
1002
  root = self.__class__()
995
1003
  stack: list[tuple[Expr, Expr]] = [(self, root)]
@@ -4554,7 +4554,7 @@ class Generator:
4554
4554
  args.append(arg_value)
4555
4555
 
4556
4556
  if self.dialect.PRESERVE_ORIGINAL_NAMES:
4557
- name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4557
+ name = expression.meta_get("name") or expression.sql_name()
4558
4558
  else:
4559
4559
  name = expression.sql_name()
4560
4560
 
@@ -5258,7 +5258,7 @@ class Generator:
5258
5258
  )
5259
5259
  return self.sql(this)
5260
5260
 
5261
- if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
5261
+ if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5262
5262
  if self.IGNORE_NULLS_BEFORE_ORDER:
5263
5263
  # The first modifier here will be the one closest to the AggFunc's arg
5264
5264
  mods = sorted(
@@ -201,7 +201,7 @@ def _levenshtein_sql(self: BigQueryGenerator, expression: exp.Levenshtein) -> st
201
201
 
202
202
 
203
203
  def _json_extract_sql(self: BigQueryGenerator, expression: JSON_EXTRACT_TYPE) -> str:
204
- name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
204
+ name = expression.meta_get("name") or expression.sql_name()
205
205
  upper = name.upper()
206
206
 
207
207
  dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS
@@ -565,7 +565,7 @@ class BigQueryGenerator(generator.Generator):
565
565
  )
566
566
 
567
567
  def column_parts(self, expression: exp.Column) -> str:
568
- if expression.meta.get("quoted_column"):
568
+ if expression.meta_get("quoted_column"):
569
569
  # If a column reference is of the form `dataset.table`.name, we need
570
570
  # to preserve the quoted table path, otherwise the reference breaks
571
571
  table_parts = ".".join(p.name for p in expression.parts[:-1])
@@ -583,7 +583,7 @@ class BigQueryGenerator(generator.Generator):
583
583
  #
584
584
  # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y` -> cross join
585
585
  # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
586
- if expression.meta.get("quoted_table"):
586
+ if expression.meta_get("quoted_table"):
587
587
  table_parts = ".".join(p.name for p in expression.parts)
588
588
  return self.sql(exp.Identifier(this=table_parts, quoted=True))
589
589
 
@@ -10,14 +10,6 @@ from sqlglot.dialects.dialect import (
10
10
  from sqlglot.generators.spark import SparkGenerator
11
11
 
12
12
 
13
- def _jsonextract_sql(
14
- self: DatabricksGenerator, expression: exp.JSONExtract | exp.JSONExtractScalar
15
- ) -> str:
16
- this = self.sql(expression, "this")
17
- expr = self.sql(expression, "expression")
18
- return f"{this}:{expr}"
19
-
20
-
21
13
  class DatabricksGenerator(SparkGenerator):
22
14
  TABLESAMPLE_SEED_KEYWORD = "REPEATABLE"
23
15
  COPY_PARAMS_ARE_WRAPPED = False
@@ -52,9 +44,10 @@ class DatabricksGenerator(SparkGenerator):
52
44
  transforms.any_to_exists,
53
45
  ]
54
46
  ),
55
- exp.JSONExtract: _jsonextract_sql,
56
- exp.JSONExtractScalar: _jsonextract_sql,
57
- exp.JSONPathRoot: lambda *_: "",
47
+ exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}:{self.sql(e, 'expression')}",
48
+ exp.JSONPathRoot: lambda self, e: (
49
+ "$" if isinstance(e.parent and e.parent.parent, exp.JSONExtractScalar) else ""
50
+ ),
58
51
  exp.ToChar: lambda self, e: (
59
52
  self.cast_sql(exp.Cast(this=e.this, to=exp.DataType(this="STRING")))
60
53
  if e.args.get("is_numeric")
@@ -98,7 +91,12 @@ class DatabricksGenerator(SparkGenerator):
98
91
 
99
92
  def jsonpath_sql(self, expression: exp.JSONPath) -> str:
100
93
  expression.set("escape", None)
101
- return super().jsonpath_sql(expression)
94
+ path = super().jsonpath_sql(expression)
95
+
96
+ if isinstance(expression.parent, exp.JSONExtractScalar):
97
+ return f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
98
+
99
+ return path
102
100
 
103
101
  def uniform_sql(self, expression: exp.Uniform) -> str:
104
102
  gen = expression.args.get("gen")
@@ -417,6 +417,15 @@ class TSQLGenerator(generator.Generator):
417
417
  # but CREATE VIEW actually requires the WITH clause to come after it so we need
418
418
  # to amend the AST by moving the CTEs to the CREATE VIEW statement's query.
419
419
  ctas_expression.set("with_", with_.pop())
420
+ elif (
421
+ kind == "FUNCTION"
422
+ and isinstance(ctas_expression, exp.Return)
423
+ and isinstance(body := ctas_expression.this.unnest(), exp.Query)
424
+ and (with_ := expression.args.get("with_"))
425
+ ):
426
+ # Similar to the VIEW branch, the table-valued functions require the WITH clause
427
+ # to stay inside the RETURN body, so we move back any CTEs that were bubbled up.
428
+ body.set("with_", with_.pop())
420
429
 
421
430
  table = expression.find(exp.Table)
422
431
 
@@ -9,6 +9,7 @@ from sqlglot import Schema, exp, maybe_parse
9
9
  from sqlglot.errors import SqlglotError
10
10
  from sqlglot.optimizer import Scope, build_scope, find_all_in_scope, normalize_identifiers, qualify
11
11
  from sqlglot.optimizer.scope import ScopeType
12
+ from sqlglot.schema import ensure_schema
12
13
 
13
14
  if t.TYPE_CHECKING:
14
15
  from sqlglot.dialects.dialect import DialectType
@@ -133,6 +134,8 @@ def lineage(
133
134
  copy=copy,
134
135
  )
135
136
 
137
+ schema = ensure_schema(schema, dialect=dialect)
138
+
136
139
  if not scope:
137
140
  expression = qualify.qualify(
138
141
  expression,
@@ -162,6 +165,7 @@ def lineage(
162
165
  scope,
163
166
  dialect,
164
167
  trim_selects=trim_selects,
168
+ schema=schema,
165
169
  _cache=cache,
166
170
  _scope_meta=scope_meta,
167
171
  on_node=on_node,
@@ -180,6 +184,7 @@ def lineage(
180
184
  scope,
181
185
  dialect,
182
186
  trim_selects=trim_selects,
187
+ schema=schema,
183
188
  _cache=cache,
184
189
  _scope_meta=scope_meta,
185
190
  on_node=on_node,
@@ -197,6 +202,7 @@ def to_node(
197
202
  source_name: str | None = None,
198
203
  reference_node_name: str | None = None,
199
204
  trim_selects: bool = True,
205
+ schema: Schema | None = None,
200
206
  _cache: dict[tuple, Node] | None = None,
201
207
  _scope_meta: dict[int, tuple[bool, dict[str, exp.Expr]]] | None = None,
202
208
  on_node: t.Callable[[Node], None] | None = None,
@@ -248,6 +254,7 @@ def to_node(
248
254
  source_name=source_name,
249
255
  reference_node_name=reference_node_name,
250
256
  trim_selects=trim_selects,
257
+ schema=schema,
251
258
  _cache=_cache,
252
259
  _scope_meta=_scope_meta,
253
260
  on_node=on_node,
@@ -288,6 +295,7 @@ def to_node(
288
295
  source_name=source_name,
289
296
  reference_node_name=reference_node_name,
290
297
  trim_selects=trim_selects,
298
+ schema=schema,
291
299
  _cache=_cache,
292
300
  _scope_meta=_scope_meta,
293
301
  on_node=on_node,
@@ -337,6 +345,7 @@ def to_node(
337
345
  dialect=dialect,
338
346
  upstream=node,
339
347
  trim_selects=trim_selects,
348
+ schema=schema,
340
349
  _cache=_cache,
341
350
  _scope_meta=_scope_meta,
342
351
  on_node=on_node,
@@ -372,25 +381,19 @@ def to_node(
372
381
  }
373
382
 
374
383
  pivots = scope.pivots
375
- pivot = pivots[0] if len(pivots) == 1 and not pivots[0].unpivot else None
384
+ pivot = pivots[0] if len(pivots) == 1 else None
385
+ pivot_renames: dict[str, str] = {}
386
+ pivot_column_mapping: dict[str, list[exp.Column]] = {}
387
+
376
388
  if pivot:
377
- # For each aggregation function, the pivot creates a new column for each field in category
378
- # combined with the aggfunc. So the columns parsed have this order: cat_a_value_sum, cat_a,
379
- # b_value_sum, b. Because of this step wise manner the aggfunc 'sum(value) as value_sum'
380
- # belongs to the column indices 0, 2, and the aggfunc 'max(price)' without an alias belongs
381
- # to the column indices 1, 3. Here, only the columns used in the aggregations are of interest
382
- # in the lineage, so lookup the pivot column name by index and map that with the columns used
383
- # in the aggregation.
384
- #
385
- # Example: PIVOT (SUM(value) AS value_sum, MAX(price)) FOR category IN ('a' AS cat_a, 'b')
386
- pivot_columns = pivot.args["columns"]
387
- pivot_aggs_count = len(pivot.expressions)
388
-
389
- pivot_column_mapping = {}
390
- for i, agg in enumerate(pivot.expressions):
391
- agg_cols = list(agg.find_all(exp.Column))
392
- for col_index in range(i, len(pivot_columns), pivot_aggs_count):
393
- pivot_column_mapping[pivot_columns[col_index].name] = agg_cols
389
+ pivot_renames = _pivot_output_renames(pivot, scope, schema)
390
+ pivot_column_mapping = _pivot_column_mapping(pivot)
391
+ if pivot_renames:
392
+ pivot_column_mapping = {
393
+ post: pivot_column_mapping[pre]
394
+ for post, pre in pivot_renames.items()
395
+ if pre in pivot_column_mapping
396
+ }
394
397
 
395
398
  for c in source_columns:
396
399
  table = c.table
@@ -414,6 +417,7 @@ def to_node(
414
417
  source_name=source_names.get(table) or source_name,
415
418
  reference_node_name=reference_node_name,
416
419
  trim_selects=trim_selects,
420
+ schema=schema,
417
421
  _cache=_cache,
418
422
  _scope_meta=_scope_meta,
419
423
  on_node=on_node,
@@ -422,19 +426,35 @@ def to_node(
422
426
  downstream_columns = []
423
427
 
424
428
  column_name = c.name
425
- if any(column_name == pivot_column.name for pivot_column in pivot_columns):
429
+ if column_name in pivot_column_mapping:
426
430
  downstream_columns.extend(pivot_column_mapping[column_name])
427
431
  else:
428
432
  # The column is not in the pivot, so it must be an implicit column of the
429
433
  # pivoted source -- adapt column to be from the implicit pivoted source.
430
434
  pivot_parent = pivot.parent
431
435
  downstream_columns.append(
432
- exp.column(c.this, table=pivot_parent.alias_or_name if pivot_parent else "")
436
+ exp.column(
437
+ pivot_renames.get(c.name, c.this),
438
+ table=pivot_parent.alias_or_name if pivot_parent else None,
439
+ )
433
440
  )
434
441
 
435
442
  for downstream_column in downstream_columns:
443
+ if not downstream_column.table:
444
+ # Some dialects (e.g. bigquery) don't qualify the IN-list columns,
445
+ # but they can only come from the pivoted source
446
+ pivot_parent = pivot.parent
447
+ downstream_column = exp.column(
448
+ downstream_column.this,
449
+ table=pivot_parent.alias_or_name if pivot_parent else None,
450
+ )
451
+
436
452
  table = downstream_column.table
437
453
  col_source = scope.sources.get(table)
454
+ if isinstance(col_source, exp.Table) and not col_source.db:
455
+ # A pivoted CTE reference maps to the raw table in `scope.sources`,
456
+ # so recover the CTE's scope to keep tracing through it
457
+ col_source = scope.cte_sources.get(col_source.name, col_source)
438
458
  if isinstance(col_source, Scope):
439
459
  to_node(
440
460
  downstream_column.name,
@@ -445,6 +465,7 @@ def to_node(
445
465
  source_name=source_names.get(table) or source_name,
446
466
  reference_node_name=reference_node_name,
447
467
  trim_selects=trim_selects,
468
+ schema=schema,
448
469
  _cache=_cache,
449
470
  _scope_meta=_scope_meta,
450
471
  on_node=on_node,
@@ -479,6 +500,89 @@ def to_node(
479
500
  return node
480
501
 
481
502
 
503
+ def _pivot_output_renames(
504
+ pivot: exp.Pivot, scope: Scope, schema: Schema | None = None
505
+ ) -> dict[str, str]:
506
+ """
507
+ Map each (UN)PIVOT output column name to its pre-rename name, when an alias column
508
+ list (`... AS t(c1, c2, ...)`) renames the outputs. The renames are positional over
509
+ the operator's full output, so they can only be aligned when the pre-pivot columns
510
+ are known: from the projections of a derived table or CTE source, or from the
511
+ schema for a physical table.
512
+ """
513
+ if not pivot.alias_column_names:
514
+ return {}
515
+
516
+ parent = pivot.parent
517
+ pre_pivot_columns: list[str] = []
518
+ if isinstance(parent, exp.DerivedTable) and isinstance(parent.this, exp.Query):
519
+ pre_pivot_columns = parent.this.named_selects
520
+ elif isinstance(parent, exp.Table):
521
+ cte_source = scope.cte_sources.get(parent.name) if not parent.db else None
522
+ if isinstance(cte_source, Scope) and isinstance(cte_source.expression, exp.Query):
523
+ pre_pivot_columns = cte_source.expression.named_selects
524
+ elif schema is not None:
525
+ pre_pivot_columns = list(schema.column_names(parent, only_visible=True))
526
+
527
+ # The alignment is also unknowable when the source's projections aren't fully
528
+ # expanded (e.g. an unresolved star), since the renames would silently shift
529
+ if not pre_pivot_columns or "*" in pre_pivot_columns:
530
+ return {}
531
+
532
+ return pivot.output_columns(pre_pivot_columns)
533
+
534
+
535
+ def _pivot_column_mapping(pivot: exp.Pivot) -> dict[str, list[exp.Column]]:
536
+ """Map each (UN)PIVOT output column name to the source columns it's derived from."""
537
+ mapping: dict[str, list[exp.Column]] = {}
538
+
539
+ if pivot.unpivot:
540
+ # UNPIVOT((v1, v2) FOR name IN ((a1, a2), (b1, b2))): each value column is derived
541
+ # positionally from the IN-list entries, and the name column from all of them
542
+ value_columns = [
543
+ identifier for e in pivot.expressions for identifier in e.find_all(exp.Identifier)
544
+ ]
545
+ for value_column in value_columns:
546
+ mapping[value_column.name] = []
547
+
548
+ for field in pivot.fields:
549
+ if not isinstance(field, exp.In):
550
+ continue
551
+
552
+ name_columns = mapping.setdefault(field.this.name, [])
553
+ for entry in field.expressions:
554
+ entry_columns = list(entry.find_all(exp.Column))
555
+ name_columns.extend(entry_columns)
556
+
557
+ if len(entry_columns) == len(value_columns):
558
+ for value_column, column in zip(value_columns, entry_columns):
559
+ mapping[value_column.name].append(column)
560
+ else:
561
+ for value_column in value_columns:
562
+ mapping[value_column.name].extend(entry_columns)
563
+
564
+ return mapping
565
+
566
+ # For each aggregation function, the pivot creates a new column for each field in category
567
+ # combined with the aggfunc. So the columns parsed have this order: cat_a_value_sum, cat_a,
568
+ # b_value_sum, b. Because of this step wise manner the aggfunc 'sum(value) as value_sum'
569
+ # belongs to the column indices 0, 2, and the aggfunc 'max(price)' without an alias belongs
570
+ # to the column indices 1, 3. Here, only the columns used in the aggregations are of interest
571
+ # in the lineage, so lookup the pivot column name by index and map that with the columns used
572
+ # in the aggregation.
573
+ #
574
+ # Example: PIVOT (SUM(value) AS value_sum, MAX(price)) FOR category IN ('a' AS cat_a, 'b')
575
+ pivot_columns = pivot.args["columns"]
576
+ pivot_aggs_count = len(pivot.expressions)
577
+
578
+ mapping = {}
579
+ for i, agg in enumerate(pivot.expressions):
580
+ agg_cols = list(agg.find_all(exp.Column))
581
+ for col_index in range(i, len(pivot_columns), pivot_aggs_count):
582
+ mapping[pivot_columns[col_index].name] = agg_cols
583
+ return mapping
584
+
585
+
482
586
  class GraphHTML:
483
587
  """Node to HTML generator using vis.js.
484
588
 
@@ -400,7 +400,7 @@ class TypeAnnotator:
400
400
  isinstance(source, Scope)
401
401
  and isinstance(source.expression, exp.Query)
402
402
  and (
403
- source.expression.meta.get("query_type") or exp.DType.UNKNOWN.into_expr()
403
+ source.expression.meta_get("query_type") or exp.DType.UNKNOWN.into_expr()
404
404
  ).is_type(exp.DType.STRUCT)
405
405
  ):
406
406
  self._set_type(table_column, source.expression.meta["query_type"])
@@ -497,7 +497,7 @@ class TypeAnnotator:
497
497
  else:
498
498
  self._set_type(expr, exp.DType.UNKNOWN)
499
499
 
500
- if expr.is_type(exp.DType.JSON) and (dot_parts := expr.meta.get("dot_parts")):
500
+ if expr.is_type(exp.DType.JSON) and (dot_parts := expr.meta_get("dot_parts")):
501
501
  # JSON dot access is case sensitive across all dialects, so we need to undo the normalization.
502
502
  i = iter(dot_parts)
503
503
  parent = expr.parent
@@ -740,7 +740,7 @@ class TypeAnnotator:
740
740
  self._annotate_by_args(expression, left, right)
741
741
 
742
742
  if isinstance(expression, exp.Is) or (
743
- left.meta.get("nonnull") is True and right.meta.get("nonnull") is True
743
+ left.meta_get("nonnull") is True and right.meta_get("nonnull") is True
744
744
  ):
745
745
  expression.meta["nonnull"] = True
746
746
 
@@ -752,7 +752,7 @@ class TypeAnnotator:
752
752
  else:
753
753
  self._set_type(expression, expression.this.type)
754
754
 
755
- if expression.this.meta.get("nonnull") is True:
755
+ if expression.this.meta_get("nonnull") is True:
756
756
  expression.meta["nonnull"] = True
757
757
 
758
758
  return expression
@@ -63,8 +63,8 @@ def normalize_identifiers(expression, dialect=None, store_original_column_identi
63
63
  if isinstance(expression, str):
64
64
  expression = exp.parse_identifier(expression, dialect=dialect)
65
65
 
66
- for node in expression.walk(prune=lambda n: bool(n.meta.get("case_sensitive"))):
67
- if not node.meta.get("case_sensitive"):
66
+ for node in expression.walk(prune=lambda n: bool(n.meta_get("case_sensitive"))):
67
+ if not node.meta_get("case_sensitive"):
68
68
  if store_original_column_identifiers and isinstance(node, exp.Column):
69
69
  # TODO: This does not handle non-column cases, e.g PARSE_JSON(...).key
70
70
  parent = node
@@ -73,6 +73,7 @@ def normalize_identifiers(expression, dialect=None, store_original_column_identi
73
73
 
74
74
  node.meta["dot_parts"] = [p.name for p in parent.parts]
75
75
 
76
- dialect.normalize_identifier(node)
76
+ if isinstance(node, exp.Identifier):
77
+ dialect.normalize_identifier(node)
77
78
 
78
79
  return expression
@@ -124,10 +124,10 @@ def validate_qualify_columns(expression: E, sql: str | None = None) -> E:
124
124
  if scope.external_columns and not scope.is_correlated_subquery and not scope.pivots:
125
125
  column = scope.external_columns[0]
126
126
  for_table = f" for table: '{column.table}'" if column.table else ""
127
- line = column.this.meta.get("line")
128
- col = column.this.meta.get("col")
129
- start = column.this.meta.get("start")
130
- end = column.this.meta.get("end")
127
+ line = column.this.meta_get("line")
128
+ col = column.this.meta_get("col")
129
+ start = column.this.meta_get("start")
130
+ end = column.this.meta_get("end")
131
131
 
132
132
  error_msg = f"Column '{column.name}' could not be resolved{for_table}."
133
133
  if line and col:
@@ -142,10 +142,10 @@ def validate_qualify_columns(expression: E, sql: str | None = None) -> E:
142
142
 
143
143
  if all_unqualified_columns:
144
144
  first_column = all_unqualified_columns[0]
145
- line = first_column.this.meta.get("line")
146
- col = first_column.this.meta.get("col")
147
- start = first_column.this.meta.get("start")
148
- end = first_column.this.meta.get("end")
145
+ line = first_column.this.meta_get("line")
146
+ col = first_column.this.meta_get("col")
147
+ start = first_column.this.meta_get("start")
148
+ end = first_column.this.meta_get("end")
149
149
 
150
150
  error_msg = f"Ambiguous column '{first_column.name}'"
151
151
  if line and col:
@@ -204,6 +204,9 @@ def _expand_using(scope: Scope, resolver: Resolver) -> dict[str, t.Any]:
204
204
  columns[column_name] = source_name
205
205
 
206
206
  joins = list(scope.find_all(exp.Join))
207
+ if not joins:
208
+ return {}
209
+
207
210
  names = {join.alias_or_name for join in joins}
208
211
  ordered = [key for key in scope.selected_sources if key not in names]
209
212
 
@@ -213,6 +216,9 @@ def _expand_using(scope: Scope, resolver: Resolver) -> dict[str, t.Any]:
213
216
  # Mapping of automatically joined column names to an ordered set of source names (dict).
214
217
  column_tables: dict[str, dict[str, t.Any]] = {}
215
218
 
219
+ if not any(join.args.get("using") for join in joins):
220
+ return column_tables
221
+
216
222
  for source_name in ordered:
217
223
  _update_source_columns(source_name)
218
224
 
@@ -954,9 +960,15 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr) -> None:
954
960
 
955
961
  def quote_identifiers(expression: E, dialect: DialectType = None, identify: bool = True) -> E:
956
962
  """Makes sure all identifiers that need to be quoted are quoted."""
957
- return expression.transform(
958
- Dialect.get_or_raise(dialect).quote_identifier, identify=identify, copy=False
959
- )
963
+ dialect = Dialect.get_or_raise(dialect)
964
+
965
+ # `quote_identifier` only mutates identifiers in place, so we avoid `transform` here
966
+ # because its node replacement machinery is wasteful for this case.
967
+ for node in expression.walk():
968
+ if isinstance(node, exp.Identifier):
969
+ dialect.quote_identifier(node, identify=identify)
970
+
971
+ return expression
960
972
 
961
973
 
962
974
  def pushdown_cte_alias_columns(scope: Scope) -> None:
@@ -157,9 +157,8 @@ class Resolver:
157
157
  ):
158
158
  columns = source_expr.named_selects
159
159
 
160
- # in bigquery, unnest structs are automatically scoped as tables, so you can
161
- # directly select a struct field in a query.
162
- # this handles the case where the unnest is statically defined.
160
+ # in bigquery, unnest structs are automatically scoped as tables, so you can directly select
161
+ # a struct field in a query. This handles the case where the unnest is statically defined.
163
162
  if self.dialect.UNNEST_COLUMN_ONLY and isinstance(source_expr, exp.Unnest):
164
163
  if not source_expr.type or source_expr.type.is_type(exp.DType.UNKNOWN):
165
164
  unnest_expr = seq_get(source_expr.expressions, 0)
@@ -178,7 +177,10 @@ class Resolver:
178
177
  ):
179
178
  explode_col = source_expr.this.this
180
179
 
181
- if isinstance(explode_col, exp.Column) and source.parent:
180
+ # If the column is unqualified at this point, it couldn't be resolved when
181
+ # this scope's children were qualified; disambiguating it here would require
182
+ # enumerating this very source's columns, i.e recurse without bound
183
+ if isinstance(explode_col, exp.Column) and explode_col.table and source.parent:
182
184
  col_type = self._get_unnest_column_type(explode_col, source.parent)
183
185
  columns.extend(self._struct_field_names(col_type))
184
186
  elif isinstance(source, Scope) and isinstance(source.expression, exp.SetOperation):
@@ -21,6 +21,20 @@ TRAVERSABLES = (exp.Query, exp.DDL, exp.DML)
21
21
 
22
22
  ROW_LEVEL_AGG_FUNCS = (exp.Count,)
23
23
 
24
+ # The node types `Scope._collect` classifies, roughly ordered by frequency. `exp.Query`
25
+ # covers subqueries, and `exp.UDTF` covers laterals.
26
+ COLLECTIBLE_TYPES = (
27
+ exp.Column,
28
+ exp.Dot,
29
+ exp.Table,
30
+ exp.Query,
31
+ exp.UDTF,
32
+ exp.CTE,
33
+ exp.Star,
34
+ exp.TableColumn,
35
+ exp.JoinHint,
36
+ )
37
+
24
38
 
25
39
  class ScopeType(Enum):
26
40
  ROOT = auto()
@@ -170,7 +184,9 @@ class Scope:
170
184
  self._column_index = set()
171
185
 
172
186
  for node in self.walk():
173
- if node is self.expression:
187
+ # Most nodes (identifiers, literals, operators etc.) aren't collectible, so a
188
+ # single isinstance gate lets them skip the classification chain below.
189
+ if node is self.expression or not isinstance(node, COLLECTIBLE_TYPES):
174
190
  continue
175
191
 
176
192
  if isinstance(node, exp.Dot) and node.is_star:
@@ -460,8 +476,10 @@ class Scope:
460
476
  list[exp.Column]: Column instances that reference sources in the current scope.
461
477
  """
462
478
  if self._local_columns is None:
463
- external_columns = set(self.external_columns)
464
- self._local_columns = [c for c in self.columns if c not in external_columns]
479
+ # Compare nodes by identity: structural equality would conflate distinct
480
+ # column nodes that happen to look the same, and is much more expensive.
481
+ external_column_ids = {id(c) for c in self.external_columns}
482
+ self._local_columns = [c for c in self.columns if id(c) not in external_column_ids]
465
483
 
466
484
  return self._local_columns
467
485
 
@@ -950,11 +968,17 @@ def walk_in_scope(
950
968
 
951
969
  yield node
952
970
 
953
- if node is not expression and (
954
- isinstance(node, exp.CTE)
955
- or (isinstance(node.parent, (exp.From, exp.Join)) and _is_derived_table(node))
956
- or (isinstance(node.parent, exp.UDTF) and isinstance(node, exp.Query))
957
- or isinstance(node, exp.UNWRAPPED_QUERIES)
971
+ # Only CTEs and Queries can start child scopes; checking that first lets all
972
+ # other nodes (the vast majority) skip the rest of the boundary checks.
973
+ if (
974
+ node is not expression
975
+ and isinstance(node, (exp.CTE, exp.Query))
976
+ and (
977
+ isinstance(node, exp.CTE)
978
+ or (isinstance(node.parent, (exp.From, exp.Join)) and _is_derived_table(node))
979
+ or isinstance(node.parent, exp.UDTF)
980
+ or isinstance(node, exp.UNWRAPPED_QUERIES)
981
+ )
958
982
  ):
959
983
  if isinstance(node, (exp.Subquery, exp.UDTF)):
960
984
  for key in ("joins", "laterals", "pivots"):
@@ -596,9 +596,9 @@ class Simplifier:
596
596
  joins: list[exp.Join] = []
597
597
 
598
598
  for node in expression.walk(
599
- prune=lambda n: bool(isinstance(n, exp.Condition) or n.meta.get(FINAL))
599
+ prune=lambda n: bool(isinstance(n, exp.Condition) or n.meta_get(FINAL))
600
600
  ):
601
- if node.meta.get(FINAL):
601
+ if node.meta_get(FINAL):
602
602
  continue
603
603
 
604
604
  # group by expressions cannot be simplified, for example
@@ -687,10 +687,10 @@ class Simplifier:
687
687
  original.replace(node)
688
688
 
689
689
  for n in node.iter_expressions(reverse=True):
690
- if n.meta.get(FINAL):
690
+ if n.meta_get(FINAL):
691
691
  raise
692
692
  pre_transformation_stack.extend(
693
- n for n in node.iter_expressions(reverse=True) if not n.meta.get(FINAL)
693
+ n for n in node.iter_expressions(reverse=True) if not n.meta_get(FINAL)
694
694
  )
695
695
  post_transformation_stack.append((node, parent))
696
696
 
@@ -699,9 +699,16 @@ class Simplifier:
699
699
  root = original is expression
700
700
 
701
701
  # Resets parent, arg_key, index pointers– this is needed because some of the
702
- # previous transformations mutate the AST, leading to an inconsistent state
702
+ # previous transformations mutate the AST, leading to an inconsistent state.
703
+ # We only fix pointers instead of calling `set` because the values are unchanged:
704
+ # actual mutations go through `set`/`replace`, which already invalidate cached
705
+ # hashes, so clearing them again here would force `while_changing` to rehash
706
+ # entire subtrees after every (mostly no-op) pass.
703
707
  for k, v in tuple(original.args.items()):
704
- original.set(k, v)
708
+ if v is None:
709
+ original.args.pop(k)
710
+ else:
711
+ original._set_parent(k, v)
705
712
 
706
713
  # Post-order transformations
707
714
  node = self.simplify_not(original)
@@ -937,7 +944,7 @@ class Simplifier:
937
944
  ops = set(expression.flatten())
938
945
  for op in ops:
939
946
  if isinstance(op, exp.Not) and op.this in ops:
940
- if expression.meta.get("nonnull") is True:
947
+ if expression.meta_get("nonnull") is True:
941
948
  return exp.false() if isinstance(expression, exp.And) else exp.true()
942
949
 
943
950
  return expression
@@ -1289,15 +1296,15 @@ class Simplifier:
1289
1296
  sep_expr, *expressions = expression.expressions
1290
1297
  sep = sep_expr.name
1291
1298
  concat_type = exp.ConcatWs
1292
- args = {}
1293
1299
  else:
1294
1300
  expressions = expression.expressions
1295
1301
  sep = ""
1296
1302
  concat_type = exp.Concat
1297
- args = {
1298
- "safe": expression.args.get("safe"),
1299
- "coalesce": expression.args.get("coalesce"),
1300
- }
1303
+
1304
+ args = {
1305
+ "safe": expression.args.get("safe"),
1306
+ "coalesce": expression.args.get("coalesce"),
1307
+ }
1301
1308
 
1302
1309
  new_args = []
1303
1310
  for is_string_group, group in itertools.groupby(
@@ -480,10 +480,10 @@ class BigQueryParser(parser.Parser):
480
480
 
481
481
  info_schema_view = f"{table_parts[-2].name}.{table_parts[-1].name}"
482
482
  new_this = exp.Identifier(this=info_schema_view, quoted=True).update_positions(
483
- line=table_parts[-2].meta.get("line"),
484
- col=table_parts[-1].meta.get("col"),
485
- start=table_parts[-2].meta.get("start"),
486
- end=table_parts[-1].meta.get("end"),
483
+ line=table_parts[-2].meta_get("line"),
484
+ col=table_parts[-1].meta_get("col"),
485
+ start=table_parts[-2].meta_get("start"),
486
+ end=table_parts[-1].meta_get("end"),
487
487
  )
488
488
  table.set("this", new_this)
489
489
  table.set("db", seq_get(table_parts, -3))
@@ -1,13 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.10.0
3
+ Version: 30.11.0
4
4
  Summary: mypyc-compiled extensions for sqlglot
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://sqlglot.com/
8
8
  Project-URL: Repository, https://github.com/tobymao/sqlglot
9
9
  Requires-Python: >=3.10
10
+ Requires-Dist: sqlglot==30.11.0
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
12
13
  Requires-Dist: setuptools_scm; extra == "dev"
13
14
  Requires-Dist: sqlglot-mypy>=2.1.0.post2; extra == "dev"
15
+ Dynamic: requires-dist
@@ -1,3 +1,4 @@
1
+ sqlglot==30.11.0
1
2
 
2
3
  [dev]
3
4
  setuptools>=61.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes