sqlglotc 30.13.0__tar.gz → 30.14.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 (117) hide show
  1. {sqlglotc-30.13.0/sqlglotc.egg-info → sqlglotc-30.14.0}/PKG-INFO +2 -2
  2. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/array.py +4 -0
  3. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/core.py +1 -1
  4. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/math.py +4 -0
  5. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generator.py +9 -5
  6. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/duckdb.py +60 -0
  7. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/hive.py +20 -11
  8. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/python.py +3 -1
  9. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/tsql.py +3 -2
  10. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/canonicalize_internal_names.py +2 -1
  11. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/qualify.py +1 -1
  12. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/qualify_columns.py +56 -9
  13. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/simplify.py +11 -3
  14. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parser.py +55 -6
  15. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/duckdb.py +1 -0
  16. {sqlglotc-30.13.0 → sqlglotc-30.14.0/sqlglotc.egg-info}/PKG-INFO +2 -2
  17. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglotc.egg-info/requires.txt +1 -1
  18. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglotc.egg-info/scm_file_list.json +2 -2
  19. sqlglotc-30.14.0/sqlglotc.egg-info/scm_version.json +8 -0
  20. sqlglotc-30.13.0/sqlglotc.egg-info/scm_version.json +0 -8
  21. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/MANIFEST.in +0 -0
  22. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/pyproject.toml +0 -0
  23. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/setup.cfg +0 -0
  24. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/setup.py +0 -0
  25. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/errors.py +0 -0
  26. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/executor/table.py +0 -0
  27. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/aggregate.py +0 -0
  28. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/builders.py +0 -0
  29. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/constraints.py +0 -0
  30. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/datatypes.py +0 -0
  31. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/ddl.py +0 -0
  32. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/dml.py +0 -0
  33. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/functions.py +0 -0
  34. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/json.py +0 -0
  35. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/properties.py +0 -0
  36. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/query.py +0 -0
  37. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/string.py +0 -0
  38. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/expressions/temporal.py +0 -0
  39. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/athena.py +0 -0
  40. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/bigquery.py +0 -0
  41. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/clickhouse.py +0 -0
  42. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/databricks.py +0 -0
  43. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/dax.py +0 -0
  44. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/doris.py +0 -0
  45. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/dremio.py +0 -0
  46. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/drill.py +0 -0
  47. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/druid.py +0 -0
  48. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/dune.py +0 -0
  49. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/exasol.py +0 -0
  50. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/fabric.py +0 -0
  51. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/materialize.py +0 -0
  52. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/mysql.py +0 -0
  53. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/oracle.py +0 -0
  54. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/postgres.py +0 -0
  55. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/presto.py +0 -0
  56. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/prql.py +0 -0
  57. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/redshift.py +0 -0
  58. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/risingwave.py +0 -0
  59. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/singlestore.py +0 -0
  60. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/snowflake.py +0 -0
  61. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/solr.py +0 -0
  62. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/spark.py +0 -0
  63. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/spark2.py +0 -0
  64. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/sqlite.py +0 -0
  65. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/starrocks.py +0 -0
  66. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/tableau.py +0 -0
  67. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/teradata.py +0 -0
  68. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/generators/trino.py +0 -0
  69. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/helper.py +0 -0
  70. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/lineage.py +0 -0
  71. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/annotate_types.py +0 -0
  72. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  73. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  74. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/qualify_tables.py +0 -0
  75. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/resolver.py +0 -0
  76. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/optimizer/scope.py +0 -0
  77. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/athena.py +0 -0
  78. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/base.py +0 -0
  79. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/bigquery.py +0 -0
  80. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/clickhouse.py +0 -0
  81. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/databricks.py +0 -0
  82. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/dax.py +0 -0
  83. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/doris.py +0 -0
  84. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/dremio.py +0 -0
  85. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/drill.py +0 -0
  86. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/druid.py +0 -0
  87. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/dune.py +0 -0
  88. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/exasol.py +0 -0
  89. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/fabric.py +0 -0
  90. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/hive.py +0 -0
  91. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/materialize.py +0 -0
  92. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/mysql.py +0 -0
  93. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/oracle.py +0 -0
  94. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/postgres.py +0 -0
  95. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/presto.py +0 -0
  96. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/prql.py +0 -0
  97. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/redshift.py +0 -0
  98. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/risingwave.py +0 -0
  99. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/singlestore.py +0 -0
  100. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/snowflake.py +0 -0
  101. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/solr.py +0 -0
  102. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/spark.py +0 -0
  103. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/spark2.py +0 -0
  104. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/sqlite.py +0 -0
  105. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/starrocks.py +0 -0
  106. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/tableau.py +0 -0
  107. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/teradata.py +0 -0
  108. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/trino.py +0 -0
  109. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/parsers/tsql.py +0 -0
  110. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/schema.py +0 -0
  111. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/serde.py +0 -0
  112. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/time.py +0 -0
  113. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/tokenizer_core.py +0 -0
  114. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglot/trie.py +0 -0
  115. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  116. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  117. {sqlglotc-30.13.0 → sqlglotc-30.14.0}/sqlglotc.egg-info/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.13.0
3
+ Version: 30.14.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.13.0
10
+ Requires-Dist: sqlglot==30.14.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
@@ -90,6 +90,10 @@ class ArraySort(Expression, Func):
90
90
  arg_types = {"this": True, "expression": False}
91
91
 
92
92
 
93
+ class Shuffle(Expression, Func):
94
+ pass
95
+
96
+
93
97
  class SortArray(Expression, Func):
94
98
  arg_types = {"this": True, "asc": False, "nulls_first": False}
95
99
 
@@ -2184,7 +2184,7 @@ class IntDiv(Expression, Binary):
2184
2184
 
2185
2185
 
2186
2186
  class Is(Expression, Binary, Predicate):
2187
- pass
2187
+ arg_types = {"this": True, "expression": True, "negate": False}
2188
2188
 
2189
2189
 
2190
2190
  class Like(Expression, Binary, Predicate):
@@ -164,6 +164,10 @@ class Log(Expression, Func):
164
164
  arg_types = {"this": True, "expression": False}
165
165
 
166
166
 
167
+ class Nanvl(Expression, Func):
168
+ arg_types = {"this": True, "expression": True}
169
+
170
+
167
171
  class Pi(Expression, Func):
168
172
  arg_types = {}
169
173
 
@@ -4440,11 +4440,11 @@ class Generator:
4440
4440
  return self.binary(expression, ">=")
4441
4441
 
4442
4442
  def is_sql(self, expression: exp.Is) -> str:
4443
+ negate = expression.args.get("negate")
4443
4444
  if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4444
- return self.sql(
4445
- expression.this if expression.expression.this else exp.not_(expression.this)
4446
- )
4447
- return self.binary(expression, "IS")
4445
+ positive = bool(expression.expression.this) != bool(negate)
4446
+ return self.sql(expression.this if positive else exp.not_(expression.this))
4447
+ return self.binary(expression, "IS NOT" if negate else "IS")
4448
4448
 
4449
4449
  def _like_sql(
4450
4450
  self,
@@ -5674,7 +5674,11 @@ class Generator:
5674
5674
 
5675
5675
  def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5676
5676
  array_agg = self.function_fallback_sql(expression)
5677
- return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5677
+ column_expr = expression.this
5678
+ if isinstance(column_expr, exp.Order):
5679
+ column_expr = column_expr.this
5680
+
5681
+ return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5678
5682
 
5679
5683
  def slice_sql(self, expression: exp.Slice) -> str:
5680
5684
  step = self.sql(expression, "step")
@@ -2204,6 +2204,25 @@ class DuckDBGenerator(generator.Generator):
2204
2204
  """
2205
2205
  )
2206
2206
 
2207
+ # BigQuery's `x IN UNNEST(arr)` NULL semantics:
2208
+ # NULL IN UNNEST([1, 2]) -> NULL
2209
+ # 3 IN UNNEST([1, NULL]) -> NULL
2210
+ # 3 IN UNNEST([1, 2]) -> FALSE
2211
+ # 1 IN UNNEST(NULL) -> FALSE (not NULL)
2212
+ # 1 IN UNNEST([]) -> FALSE
2213
+ # The default `IN (SELECT UNNEST(...))` rewrite creates a correlated subquery
2214
+ # that DuckDB rejects inside non-inner joins, so a CASE expression is used instead.
2215
+ IN_UNNEST_TEMPLATE: exp.Expr = exp.maybe_parse(
2216
+ """
2217
+ CASE
2218
+ WHEN :arr IS NULL OR ARRAY_LENGTH(:arr) = 0 THEN FALSE
2219
+ WHEN ARRAY_CONTAINS(:arr, :value) THEN TRUE
2220
+ WHEN :value IS NULL OR ARRAY_LENGTH(:arr) <> LIST_COUNT(:arr) THEN NULL
2221
+ ELSE FALSE
2222
+ END
2223
+ """
2224
+ )
2225
+
2207
2226
  STRTOK_TO_ARRAY_TEMPLATE: exp.Expr = exp.maybe_parse(
2208
2227
  """
2209
2228
  CASE WHEN :delimiter IS NULL THEN NULL
@@ -3098,6 +3117,16 @@ class DuckDBGenerator(generator.Generator):
3098
3117
 
3099
3118
  return super().tablesample_sql(expression, tablesample_keyword=tablesample_keyword)
3100
3119
 
3120
+ def in_sql(self, expression: exp.In) -> str:
3121
+ unnest = expression.args.get("unnest")
3122
+ if unnest:
3123
+ return self.sql(
3124
+ exp.replace_placeholders(
3125
+ self.IN_UNNEST_TEMPLATE, arr=unnest.expressions[0], value=expression.this
3126
+ )
3127
+ )
3128
+ return super().in_sql(expression)
3129
+
3101
3130
  def join_sql(self, expression: exp.Join) -> str:
3102
3131
  if (
3103
3132
  not expression.args.get("using")
@@ -3460,6 +3489,23 @@ class DuckDBGenerator(generator.Generator):
3460
3489
  )
3461
3490
  )
3462
3491
 
3492
+ def arrayconcatagg_sql(self, expression: exp.ArrayConcatAgg) -> str:
3493
+ this = expression.this
3494
+
3495
+ if isinstance(this, exp.Limit):
3496
+ self.unsupported("LIMIT in ARRAY_CONCAT_AGG cannot be transpiled to DuckDB")
3497
+ this = this.this
3498
+
3499
+ inner = this.this if isinstance(this, exp.Order) else this
3500
+
3501
+ return self.func(
3502
+ "FLATTEN",
3503
+ exp.Filter(
3504
+ this=exp.ArrayAgg(this=this),
3505
+ expression=exp.Where(this=inner.copy().is_(exp.null()).not_()),
3506
+ ),
3507
+ )
3508
+
3463
3509
  def arrayunionagg_sql(self, expression: exp.ArrayUnionAgg) -> str:
3464
3510
  self.unsupported("ARRAY_UNION_AGG is not supported in DuckDB")
3465
3511
  return self.function_fallback_sql(expression)
@@ -3917,6 +3963,12 @@ class DuckDBGenerator(generator.Generator):
3917
3963
 
3918
3964
  return super().unnest_sql(expression)
3919
3965
 
3966
+ def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
3967
+ if isinstance(expression.this, exp.Limit):
3968
+ self.unsupported("LIMIT inside ARRAY_AGG is not supported in DuckDB")
3969
+
3970
+ return super().arrayagg_sql(expression)
3971
+
3920
3972
  def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3921
3973
  this = expression.this
3922
3974
 
@@ -3925,6 +3977,14 @@ class DuckDBGenerator(generator.Generator):
3925
3977
  # window functions that accept it e.g. FIRST_VALUE(... IGNORE NULLS) OVER (...)
3926
3978
  return super().ignorenulls_sql(expression)
3927
3979
 
3980
+ # For ARRAY_AGG(expr IGNORE NULLS ...), convert IGNORE NULLS to a
3981
+ # FILTER(WHERE expr IS NOT NULL) clause by setting nulls_excluded on
3982
+ # the ArrayAgg. The existing _add_arrayagg_null_filter method will
3983
+ # emit the FILTER clause during arrayagg_sql / withingroup_sql.
3984
+ if isinstance(this, exp.ArrayAgg):
3985
+ this.set("nulls_excluded", True)
3986
+ return self.sql(this)
3987
+
3928
3988
  if isinstance(this, exp.First):
3929
3989
  this = exp.AnyValue(this=this.this)
3930
3990
 
@@ -45,27 +45,35 @@ HIVE_TIME_FORMAT = "'yyyy-MM-dd HH:mm:ss'"
45
45
  HIVE_DATE_FORMAT = "'yyyy-MM-dd'"
46
46
  HIVE_DATEINT_FORMAT = "'yyyyMMdd'"
47
47
 
48
- # The default formats above, as rendered by the lenient rewrite (non-padded month/day)
49
- HIVE_NON_PADDED_TIME_FORMATS = ("'yyyy-M-d HH:mm:ss'", "'yyyy-M-d'")
48
+ # The default formats above, as rendered by the lenient rewrite (non-padded month/day/time)
49
+ HIVE_NON_PADDED_TIME_FORMATS = ("'yyyy-M-d H:m:s'", "'yyyy-M-d'")
50
50
 
51
51
  # Expressions that parse a string with a format (vs. formatting one, like TimeToStr).
52
52
  PARSE_TIME_EXPRESSIONS = (exp.StrToTime, exp.StrToDate, exp.StrToUnix, exp.TsOrDsToDate)
53
53
 
54
- CANONICAL_TIME_FORMAT = re.compile(r"%(?:mstrict|dstrict|[-:].|.)")
54
+ CANONICAL_TIME_FORMAT = re.compile(r"%(?:[mdHIMS]strict|[-:].|.)")
55
55
 
56
- LAX_TO_NON_PADDED_FORMATS = {"%m": "%-m", "%d": "%-d"}
56
+ LAX_TO_NON_PADDED_FORMATS = {
57
+ "%m": "%-m",
58
+ "%d": "%-d",
59
+ "%H": "%-H",
60
+ "%I": "%-I",
61
+ "%M": "%-M",
62
+ "%S": "%-S",
63
+ }
57
64
 
58
65
 
59
66
  def _lenient_parse_format(fmt: str) -> str:
60
67
  """
61
- Changes the lax %m/%d in a canonical format to the non-padded %-m/%-d, which java.time
62
- parses with or without a leading zero. This is only safe for delimited specifiers, because
63
- adjacent fields parse greedily, so e.g. 'yyyyMd' (from '%Y%m%d') can't even parse '20200101'.
68
+ Changes a lax month/day/hour/minute/second in a canonical format to its non-padded form
69
+ (e.g. %m -> %-m), which java.time parses with or without a leading zero. This is only safe
70
+ for delimited specifiers, because adjacent fields parse greedily, so e.g. 'yyyyMd' (from
71
+ '%Y%m%d') can't even parse '20200101'.
64
72
 
65
73
  The format is decomposed into specifiers (`formats`) and the interleaved literal text (`parts`).
66
- Specifier i sits between parts[i] and parts[i + 1]. A %m/%d is changed only when its neighbors
67
- don't touch a digit run, i.e., neither side is another specifier or a literal digit. At the end,
68
- the pieces are zipped back together to produce the rewritten canonical format.
74
+ Specifier i sits between parts[i] and parts[i + 1]. A specifier is changed only when its
75
+ neighbors don't touch a digit run, i.e., neither side is another specifier or a literal digit.
76
+ At the end, the pieces are zipped back together to produce the rewritten canonical format.
69
77
  """
70
78
  parts = CANONICAL_TIME_FORMAT.split(fmt)
71
79
  formats = CANONICAL_TIME_FORMAT.findall(fmt)
@@ -175,7 +183,8 @@ def _is_cast_time_format(self: HiveGenerator, expression: exp.Expr, time_format:
175
183
  return True
176
184
 
177
185
  if time_format in HIVE_NON_PADDED_TIME_FORMATS:
178
- # The base render skips the lenient rewrite: a lax %m/%d pads back to MM/dd, an explicit %-m/%-d doesn't
186
+ # The base render skips the lenient rewrite: a lax specifier pads back (e.g. %m -> MM),
187
+ # an explicit non-padded specifier (e.g. %-m) doesn't
179
188
  padded_format = generator.Generator.format_time(self, expression)
180
189
  return padded_format in (HIVE_TIME_FORMAT, HIVE_DATE_FORMAT)
181
190
 
@@ -93,7 +93,9 @@ class PythonGenerator(generator.Generator):
93
93
  exp.In: lambda self, e: f"{self.sql(e, 'this')} in {{{self.expressions(e, flat=True)}}}",
94
94
  exp.Interval: lambda self, e: f"INTERVAL({self.sql(e.this)}, '{self.sql(e.unit)}')",
95
95
  exp.Is: lambda self, e: (
96
- self.binary(e, "==") if isinstance(e.this, exp.Literal) else self.binary(e, "is")
96
+ self.binary(e, "!=" if e.args.get("negate") else "==")
97
+ if isinstance(e.this, exp.Literal)
98
+ else self.binary(e, "is not" if e.args.get("negate") else "is")
97
99
  ),
98
100
  exp.JSONExtract: lambda self, e: self.func(e.key, e.this, e.expression, *e.expressions),
99
101
  exp.JSONPath: lambda self, e: f"[{','.join(self.sql(p) for p in e.expressions[1:])}]",
@@ -378,9 +378,10 @@ class TSQLGenerator(generator.Generator):
378
378
  return "(1 = 1)" if expression.this else "(1 = 0)"
379
379
 
380
380
  def is_sql(self, expression: exp.Is) -> str:
381
+ negate = expression.args.get("negate")
381
382
  if isinstance(expression.expression, exp.Boolean):
382
- return self.binary(expression, "=")
383
- return self.binary(expression, "IS")
383
+ return self.binary(expression, "<>" if negate else "=")
384
+ return self.binary(expression, "IS NOT" if negate else "IS")
384
385
 
385
386
  def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
386
387
  sql = self.sql(expression, "this")
@@ -245,7 +245,8 @@ def canonicalize_internal_names(expression: E) -> E:
245
245
  output_map: dict[str, str] = {}
246
246
  if isinstance(scope_expr, exp.Select):
247
247
  for sel in scope_expr.selects:
248
- if isinstance(sel, exp.Alias):
248
+ # Sets default name for subquery projections, both aliased and unaliased
249
+ if isinstance(sel, (exp.Alias, exp.Subquery)) and sel.alias:
249
250
  old_alias = sel.alias
250
251
  if is_output_scope:
251
252
  new_name = old_alias
@@ -32,7 +32,7 @@ def qualify(
32
32
  quote_identifiers: bool = True,
33
33
  identify: bool = True,
34
34
  canonicalize_table_aliases: bool = False,
35
- on_qualify: t.Callable[[exp.Expr], None] | None = None,
35
+ on_qualify: t.Callable[[exp.Table], None] | None = None,
36
36
  sql: str | None = None,
37
37
  ) -> E:
38
38
  """
@@ -819,18 +819,29 @@ def _expand_stars(
819
819
  for expression in scope_expression.selects:
820
820
  tables: list[str] = []
821
821
  if isinstance(expression, exp.Star):
822
+ # Only a string literal ILIKE pattern can filter the expansion at optimization time
823
+ ilike = expression.args.get("ilike")
824
+ if ilike and not ilike.is_string:
825
+ new_selections.append(expression)
826
+ continue
827
+
822
828
  tables.extend(scope.selected_sources)
823
829
  _add_except_columns(expression, tables, except_columns)
824
830
  _add_replace_columns(expression, tables, replace_columns)
825
831
  _add_rename_columns(expression, tables, rename_columns)
826
- ilike_pattern = _add_ilike_columns(expression)
832
+ ilike_pattern = _add_ilike_columns(expression, dialect)
827
833
  elif expression.is_star:
828
834
  if isinstance(expression, exp.Column):
835
+ ilike = expression.this.args.get("ilike")
836
+ if ilike and not ilike.is_string:
837
+ new_selections.append(expression)
838
+ continue
839
+
829
840
  tables.append(expression.table)
830
841
  _add_except_columns(expression.this, tables, except_columns)
831
842
  _add_replace_columns(expression.this, tables, replace_columns)
832
843
  _add_rename_columns(expression.this, tables, rename_columns)
833
- ilike_pattern = _add_ilike_columns(expression.this)
844
+ ilike_pattern = _add_ilike_columns(expression.this, dialect)
834
845
  elif isinstance(expression, exp.Dot):
835
846
  if dialect.REQUIRES_PARENTHESIZED_STRUCT_ACCESS:
836
847
  struct_fields = _expand_struct_stars_with_parens(expression)
@@ -861,7 +872,10 @@ def _expand_stars(
861
872
  if pseudocolumns and dialect.EXCLUDES_PSEUDOCOLUMNS_FROM_STAR:
862
873
  columns = [name for name in columns if name.upper() not in pseudocolumns]
863
874
 
864
- if not columns or "*" in columns:
875
+ # If a source exposes duplicate output names (e.g. a derived table re-exposing
876
+ # colliding star-expanded columns), expanding this star would produce ambiguous
877
+ # projections, so we leave it unexpanded.
878
+ if not columns or "*" in columns or len(columns) != len(set(columns)):
865
879
  return
866
880
 
867
881
  table_id = id(table)
@@ -943,13 +957,33 @@ def _output_identifier_quoted(selection: exp.Expr) -> bool:
943
957
  return isinstance(identifier, exp.Identifier) and identifier.quoted
944
958
 
945
959
 
946
- def _add_ilike_columns(expression: exp.Expr) -> str | None:
960
+ def _add_ilike_columns(expression: exp.Expr, dialect: Dialect) -> str | None:
947
961
  ilike = expression.args.get("ilike")
948
962
 
949
963
  if not ilike:
950
964
  return None
951
965
 
952
- return "".join(".*" if c == "%" else "." if c == "_" else re.escape(c) for c in ilike.name)
966
+ i = 0
967
+ chars = []
968
+ pattern = ilike.name
969
+ len_pattern = len(pattern)
970
+
971
+ while i < len_pattern:
972
+ c = pattern[i]
973
+
974
+ if c == "\\" and dialect.STAR_ILIKE_BACKSLASH_ESCAPE and i + 1 < len_pattern:
975
+ i += 1
976
+ chars.append(re.escape(pattern[i]))
977
+ elif c == "%":
978
+ chars.append(".*")
979
+ elif c == "_":
980
+ chars.append(".")
981
+ else:
982
+ chars.append(re.escape(c))
983
+
984
+ i += 1
985
+
986
+ return "".join(chars)
953
987
 
954
988
 
955
989
  def _add_except_columns(expression: exp.Expr, tables, except_columns: dict[int, set[str]]) -> None:
@@ -1020,15 +1054,28 @@ def qualify_outputs(scope_or_expression: Scope | exp.Expr, dialect: Dialect) ->
1020
1054
  dialect.normalize_identifier(alias_identifier)
1021
1055
  selection.set("alias", exp.TableAlias(this=alias_identifier))
1022
1056
  elif not isinstance(selection, (exp.Alias, exp.Aliases)) and not selection.is_star:
1023
- source_quoted = isinstance(selection, exp.Column) and selection.this.quoted
1057
+ unwrapped = selection.unnest()
1058
+ if isinstance(unwrapped, exp.Column):
1059
+ source_identifier = unwrapped.this
1060
+ elif isinstance(unwrapped, exp.Dot):
1061
+ source_identifier = unwrapped.expression
1062
+ else:
1063
+ source_identifier = None
1064
+
1024
1065
  selection = alias(
1025
1066
  selection,
1026
1067
  alias=selection.output_name or f"_col_{i}",
1027
1068
  copy=False,
1028
1069
  )
1029
- if source_quoted:
1030
- selection.args["alias"].set("quoted", True)
1031
- dialect.normalize_identifier(selection.args["alias"])
1070
+ if isinstance(source_identifier, exp.Identifier):
1071
+ # The alias copies the exact spelling of an existing identifier, so folding it
1072
+ # here would desync it from other occurrences of that identifier; its casing
1073
+ # is `normalize_identifiers`' concern, which has already run (or was skipped
1074
+ # deliberately) by this point
1075
+ if source_identifier.quoted:
1076
+ selection.args["alias"].set("quoted", True)
1077
+ else:
1078
+ dialect.normalize_identifier(selection.args["alias"])
1032
1079
  if aliased_column:
1033
1080
  selection.set("alias", exp.to_identifier(aliased_column))
1034
1081
 
@@ -332,6 +332,8 @@ def eval_boolean(
332
332
  expression: object, a: SupportsComparison, b: SupportsComparison
333
333
  ) -> exp.Boolean | None:
334
334
  if isinstance(expression, (exp.EQ, exp.Is)):
335
+ if isinstance(expression, exp.Is) and expression.args.get("negate"):
336
+ return boolean_literal(a != b)
335
337
  return boolean_literal(a == b)
336
338
  if isinstance(expression, exp.NEQ):
337
339
  return boolean_literal(a != b)
@@ -892,8 +894,11 @@ class Simplifier:
892
894
  self, expression: exp.Expr, left: exp.Expr, right: exp.Expr, or_: bool = False
893
895
  ) -> exp.Expr | None:
894
896
  if isinstance(left, self.COMPARISONS) and isinstance(right, self.COMPARISONS):
895
- ll, lr = left.args.values()
896
- rl, rr = right.args.values()
897
+ if any(isinstance(e, exp.Is) and e.args.get("negate") for e in (left, right)):
898
+ return None
899
+
900
+ ll, lr = left.this, left.expression
901
+ rl, rr = right.this, right.expression
897
902
 
898
903
  largs = {ll, lr}
899
904
  rargs = {rl, rr}
@@ -1194,6 +1199,9 @@ class Simplifier:
1194
1199
  c = b
1195
1200
  not_ = False
1196
1201
 
1202
+ if expression.args.get("negate"):
1203
+ not_ = not not_
1204
+
1197
1205
  if is_null(c):
1198
1206
  if isinstance(a, exp.Literal):
1199
1207
  return exp.true() if not_ else exp.false()
@@ -1699,7 +1707,7 @@ class Gen:
1699
1707
  self._binary(e, " DIV ")
1700
1708
 
1701
1709
  def is_sql(self, e: exp.Is) -> None:
1702
- self._binary(e, " IS ")
1710
+ self._binary(e, " IS NOT " if e.args.get("negate") else " IS ")
1703
1711
 
1704
1712
  def like_sql(self, e: exp.Like) -> None:
1705
1713
  self._binary(e, " NOT Like " if e.args.get("negate") else " Like ")
@@ -742,6 +742,7 @@ class Parser:
742
742
  TokenType.OFFSET,
743
743
  TokenType.OPERATOR,
744
744
  TokenType.ORDINALITY,
745
+ TokenType.OUT,
745
746
  TokenType.OVER,
746
747
  TokenType.OVERLAPS,
747
748
  TokenType.OVERWRITE,
@@ -1018,7 +1019,10 @@ class Parser:
1018
1019
  )
1019
1020
  ),
1020
1021
  TokenType.FARROW: lambda self, expressions: self.expression(
1021
- exp.Kwarg(this=exp.var(expressions[0].name), expression=self._parse_disjunction())
1022
+ exp.Kwarg(
1023
+ this=exp.var(expressions[0].name),
1024
+ expression=self._parse_disjunction() or self._parse_select(),
1025
+ )
1022
1026
  ),
1023
1027
  }
1024
1028
 
@@ -3542,6 +3546,38 @@ class Parser:
3542
3546
 
3543
3547
  this = self._parse_function() if is_function else self._parse_insert_table()
3544
3548
 
3549
+ # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
3550
+ set_values = None
3551
+ if self._match(TokenType.SET):
3552
+ columns = []
3553
+ values = []
3554
+
3555
+ def _parse_set_assignment() -> exp.Expr | None:
3556
+ target = self._parse_column()
3557
+ if isinstance(target, exp.Column) and self._match(TokenType.EQ):
3558
+ if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
3559
+ value: exp.Expr | None = exp.var(self._prev.text.upper())
3560
+ else:
3561
+ value = self._parse_disjunction()
3562
+
3563
+ if value:
3564
+ columns.append(target.this)
3565
+ values.append(value)
3566
+ return value
3567
+
3568
+ self.raise_error("Expected column assignment in INSERT ... SET")
3569
+ return None
3570
+
3571
+ self._parse_csv(_parse_set_assignment)
3572
+
3573
+ this = self.expression(exp.Schema(this=this, expressions=columns))
3574
+ set_values = self.expression(
3575
+ exp.Values(
3576
+ expressions=[exp.Tuple(expressions=values)],
3577
+ alias=self._parse_table_alias(),
3578
+ )
3579
+ )
3580
+
3545
3581
  returning = self._parse_returning() # TSQL allows RETURNING before source
3546
3582
 
3547
3583
  return self.expression(
@@ -3557,7 +3593,9 @@ class Parser:
3557
3593
  partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
3558
3594
  settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
3559
3595
  default=self._match_text_seq("DEFAULT", "VALUES"),
3560
- expression=self._parse_derived_table_values() or self._parse_ddl_select(),
3596
+ expression=set_values
3597
+ or self._parse_derived_table_values()
3598
+ or self._parse_ddl_select(),
3561
3599
  conflict=self._parse_on_conflict(),
3562
3600
  returning=returning or self._parse_returning(),
3563
3601
  overwrite=overwrite,
@@ -5912,8 +5950,11 @@ class Parser:
5912
5950
  elif self._match(TokenType.NOTNULL):
5913
5951
  # Postgres supports ISNULL and NOTNULL for conditions.
5914
5952
  # https://blog.andreiavram.ro/postgresql-null-composite-type/
5915
- this = self.expression(exp.Is(this=this, expression=exp.Null()))
5916
- this = self.expression(exp.Not(this=this))
5953
+ if self.dialect.NORMALIZE_NOT_NULL:
5954
+ this = self.expression(exp.Is(this=this, expression=exp.Null()))
5955
+ this = self.expression(exp.Not(this=this))
5956
+ else:
5957
+ this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
5917
5958
  else:
5918
5959
  if negate:
5919
5960
  self._retreat(self._index - 1)
@@ -5969,8 +6010,12 @@ class Parser:
5969
6010
  self._retreat(index)
5970
6011
  return None
5971
6012
 
5972
- this = self.expression(exp.Is(this=this, expression=expression))
5973
- this = self.expression(exp.Not(this=this)) if negate else this
6013
+ if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
6014
+ this = self.expression(exp.Is(this=this, expression=expression, negate=True))
6015
+ else:
6016
+ this = self.expression(exp.Is(this=this, expression=expression))
6017
+ this = self.expression(exp.Not(this=this)) if negate else this
6018
+
5974
6019
  return self._parse_column_ops(this)
5975
6020
 
5976
6021
  def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
@@ -9737,7 +9782,11 @@ class Parser:
9737
9782
  this.set("unpack", True)
9738
9783
  return this
9739
9784
 
9785
+ index = self._index
9740
9786
  ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
9787
+ if not ilike:
9788
+ # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
9789
+ self._retreat(index)
9741
9790
 
9742
9791
  return self.expression(
9743
9792
  exp.Star(
@@ -129,6 +129,7 @@ class DuckDBParser(parser.Parser):
129
129
  ),
130
130
  "EPOCH": exp.TimeToUnix.from_arg_list,
131
131
  "EPOCH_MS": lambda args: exp.UnixToTime(this=seq_get(args, 0), scale=exp.UnixToTime.MILLIS),
132
+ "FROM_HEX": exp.Unhex.from_arg_list,
132
133
  "GENERATE_SERIES": _build_generate_series(),
133
134
  "GET_CURRENT_TIME": exp.CurrentTime.from_arg_list,
134
135
  "GET_BIT": lambda args: exp.Getbit(
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglotc
3
- Version: 30.13.0
3
+ Version: 30.14.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.13.0
10
+ Requires-Dist: sqlglot==30.14.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
@@ -1,4 +1,4 @@
1
- sqlglot==30.13.0
1
+ sqlglot==30.14.0
2
2
 
3
3
  [dev]
4
4
  setuptools>=61.0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "files": [
3
- "pyproject.toml",
4
3
  "MANIFEST.in",
5
- "setup.py"
4
+ "setup.py",
5
+ "pyproject.toml"
6
6
  ]
7
7
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "30.14.0",
3
+ "distance": 0,
4
+ "node": "g29c651b85309693924b8c034501e6a2733d14588",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-27"
8
+ }
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "30.13.0",
3
- "distance": 0,
4
- "node": "g98e2b3fa99d101ec57da18ebcbf3e147406dc966",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-20"
8
- }
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
File without changes