sqlglotc 30.19.0__tar.gz → 30.21.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.19.0/sqlglotc.egg-info → sqlglotc-30.21.0}/PKG-INFO +2 -2
  2. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/json.py +1 -1
  3. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/query.py +13 -0
  4. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generator.py +43 -3
  5. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/athena.py +1 -1
  6. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/bigquery.py +5 -0
  7. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/clickhouse.py +1 -0
  8. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/duckdb.py +7 -5
  9. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/mysql.py +2 -2
  10. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/singlestore.py +6 -1
  11. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/spark.py +3 -3
  12. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/spark2.py +10 -0
  13. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/sqlite.py +13 -6
  14. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/starrocks.py +19 -0
  15. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/canonicalize_internal_names.py +6 -4
  16. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/qualify_columns.py +40 -15
  17. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/resolver.py +16 -12
  18. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/simplify.py +5 -2
  19. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parser.py +30 -11
  20. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/athena.py +5 -2
  21. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/clickhouse.py +9 -6
  22. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/hive.py +37 -1
  23. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/spark.py +2 -0
  24. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/spark2.py +2 -0
  25. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/teradata.py +2 -1
  26. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/tokenizer_core.py +110 -4
  27. {sqlglotc-30.19.0 → sqlglotc-30.21.0/sqlglotc.egg-info}/PKG-INFO +2 -2
  28. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglotc.egg-info/requires.txt +1 -1
  29. sqlglotc-30.21.0/sqlglotc.egg-info/scm_version.json +8 -0
  30. sqlglotc-30.19.0/sqlglotc.egg-info/scm_version.json +0 -8
  31. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/MANIFEST.in +0 -0
  32. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/pyproject.toml +0 -0
  33. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/setup.cfg +0 -0
  34. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/setup.py +0 -0
  35. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/anonymize.py +0 -0
  36. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/errors.py +0 -0
  37. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/executor/table.py +0 -0
  38. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/aggregate.py +0 -0
  39. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/array.py +0 -0
  40. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/builders.py +0 -0
  41. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/constraints.py +0 -0
  42. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/core.py +0 -0
  43. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/datatypes.py +0 -0
  44. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/ddl.py +0 -0
  45. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/dml.py +0 -0
  46. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/functions.py +0 -0
  47. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/math.py +0 -0
  48. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/properties.py +0 -0
  49. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/string.py +0 -0
  50. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/expressions/temporal.py +0 -0
  51. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/databricks.py +0 -0
  52. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/dax.py +0 -0
  53. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/doris.py +0 -0
  54. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/dremio.py +0 -0
  55. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/drill.py +0 -0
  56. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/druid.py +0 -0
  57. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/dune.py +0 -0
  58. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/exasol.py +0 -0
  59. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/fabric.py +0 -0
  60. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/hive.py +0 -0
  61. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/materialize.py +0 -0
  62. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/oracle.py +0 -0
  63. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/postgres.py +0 -0
  64. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/presto.py +0 -0
  65. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/prql.py +0 -0
  66. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/python.py +0 -0
  67. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/redshift.py +0 -0
  68. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/risingwave.py +0 -0
  69. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/snowflake.py +0 -0
  70. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/solr.py +0 -0
  71. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/tableau.py +0 -0
  72. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/teradata.py +0 -0
  73. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/trino.py +0 -0
  74. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/generators/tsql.py +0 -0
  75. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/helper.py +0 -0
  76. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/lineage.py +0 -0
  77. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/annotate_types.py +0 -0
  78. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  79. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  80. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/qualify.py +0 -0
  81. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/qualify_tables.py +0 -0
  82. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/optimizer/scope.py +0 -0
  83. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/base.py +0 -0
  84. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/bigquery.py +0 -0
  85. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/databricks.py +0 -0
  86. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/dax.py +0 -0
  87. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/doris.py +0 -0
  88. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/dremio.py +0 -0
  89. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/drill.py +0 -0
  90. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/druid.py +0 -0
  91. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/duckdb.py +0 -0
  92. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/dune.py +0 -0
  93. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/exasol.py +0 -0
  94. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/fabric.py +0 -0
  95. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/materialize.py +0 -0
  96. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/mysql.py +0 -0
  97. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/oracle.py +0 -0
  98. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/postgres.py +0 -0
  99. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/presto.py +0 -0
  100. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/prql.py +0 -0
  101. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/redshift.py +0 -0
  102. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/risingwave.py +0 -0
  103. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/singlestore.py +0 -0
  104. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/snowflake.py +0 -0
  105. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/solr.py +0 -0
  106. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/sqlite.py +0 -0
  107. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/starrocks.py +0 -0
  108. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/tableau.py +0 -0
  109. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/trino.py +0 -0
  110. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/parsers/tsql.py +0 -0
  111. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/schema.py +0 -0
  112. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/serde.py +0 -0
  113. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/time.py +0 -0
  114. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglot/trie.py +0 -0
  115. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  116. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  117. {sqlglotc-30.19.0 → sqlglotc-30.21.0}/sqlglotc.egg-info/scm_file_list.json +0 -0
  118. {sqlglotc-30.19.0 → sqlglotc-30.21.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.19.0
3
+ Version: 30.21.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.19.0
10
+ Requires-Dist: sqlglot==30.21.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
@@ -248,4 +248,4 @@ class ParseJSON(Expression, Func):
248
248
  # BigQuery, Snowflake have PARSE_JSON, Presto has JSON_PARSE
249
249
  # Snowflake also has TRY_PARSE_JSON, which is represented using `safe`
250
250
  _sql_names = ["PARSE_JSON", "JSON_PARSE"]
251
- arg_types = {"this": True, "expression": False, "safe": False}
251
+ arg_types = {"this": True, "expression": False, "safe": False, "is_literal": False}
@@ -804,6 +804,19 @@ class Lateral(Expression, UDTF):
804
804
  "ordinality": False,
805
805
  }
806
806
 
807
+ @property
808
+ def selects(self) -> list[Expr]:
809
+ from sqlglot.expressions.array import Unnest
810
+
811
+ columns = super().selects
812
+
813
+ # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
814
+ offset = self.this.args.get("offset") if isinstance(self.this, Unnest) else None
815
+ if isinstance(offset, Identifier):
816
+ columns = columns + [offset]
817
+
818
+ return columns
819
+
807
820
 
808
821
  class TableFromRows(Expression, UDTF):
809
822
  arg_types = {
@@ -2959,7 +2959,20 @@ class Generator:
2959
2959
  op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2960
2960
  return f"{op_sql}{self.sep()}{this}{table}{columns}"
2961
2961
 
2962
- alias = self.sql(expression, "alias")
2962
+ table_alias = expression.args.get("alias")
2963
+ offset = expression.this.args.get("offset")
2964
+
2965
+ if (
2966
+ self.UNNEST_WITH_ORDINALITY
2967
+ and table_alias
2968
+ and isinstance(expression.this, exp.Unnest)
2969
+ and isinstance(offset, exp.Identifier)
2970
+ ):
2971
+ # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
2972
+ table_alias = table_alias.copy()
2973
+ table_alias.append("columns", offset.copy())
2974
+
2975
+ alias = self.sql(table_alias)
2963
2976
  alias = f" AS {alias}" if alias else ""
2964
2977
 
2965
2978
  ordinality = expression.args.get("ordinality") or ""
@@ -4942,15 +4955,20 @@ class Generator:
4942
4955
  def tonumber_sql(self, expression: exp.ToNumber) -> str:
4943
4956
  if not self.SUPPORTS_TO_NUMBER:
4944
4957
  self.unsupported("Unsupported TO_NUMBER function")
4945
- return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4958
+ return self._tonumber_cast_sql(expression)
4946
4959
 
4947
4960
  fmt = expression.args.get("format")
4948
4961
  if not fmt:
4949
4962
  self.unsupported("Conversion format is required for TO_NUMBER")
4950
- return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4963
+ return self._tonumber_cast_sql(expression)
4951
4964
 
4952
4965
  return self.func("TO_NUMBER", expression.this, fmt)
4953
4966
 
4967
+ def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
4968
+ if expression.args.get("safe"):
4969
+ return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
4970
+ return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4971
+
4954
4972
  def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4955
4973
  this = self.sql(expression, "this")
4956
4974
  kind = self.sql(expression, "kind")
@@ -5304,6 +5322,28 @@ class Generator:
5304
5322
  sqlglot.dialects.dialect.unit_to_str(expression),
5305
5323
  )
5306
5324
 
5325
+ def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5326
+ this = expression.this
5327
+ position = expression.args["position"]
5328
+ offset = index_offset - (expression.args.get("offset") or 0)
5329
+
5330
+ if offset:
5331
+ if position.is_int:
5332
+ value = position.to_py()
5333
+ if value >= 0:
5334
+ position = exp.Literal.number(value + offset)
5335
+ elif offset < 0 and value == -1:
5336
+ # 1-based -1 appends, which a 0-based position can only express as the size
5337
+ position = exp.ArraySize(this=this.copy())
5338
+ else:
5339
+ # Negative positions count from the end, so they shift in the opposite
5340
+ # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5341
+ position = exp.Literal.number(value - offset)
5342
+ else:
5343
+ self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5344
+
5345
+ return self.func("ARRAY_INSERT", this, position, expression.expression)
5346
+
5307
5347
  def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5308
5348
  if self.CAN_IMPLEMENT_ARRAY_ANY:
5309
5349
  filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
@@ -122,7 +122,7 @@ class AthenaTrinoGenerator(TrinoGenerator):
122
122
  }
123
123
 
124
124
 
125
- class AthenaGenerator(generator.Generator):
125
+ class AthenaGenerator(AthenaTrinoGenerator):
126
126
  SELECT_KINDS: tuple[str, ...] = ()
127
127
  SUPPORTS_DECODE_CASE = False
128
128
 
@@ -713,6 +713,11 @@ class BigQueryGenerator(generator.Generator):
713
713
 
714
714
  return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
715
715
 
716
+ def parsejson_sql(self, expression: exp.ParseJSON) -> str:
717
+ if expression.args.get("is_literal"):
718
+ return f"JSON {self.sql(expression, 'this')}"
719
+ return super().parsejson_sql(expression)
720
+
716
721
  def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
717
722
  this = expression.this
718
723
 
@@ -370,6 +370,7 @@ class ClickHouseGenerator(generator.Generator):
370
370
  exp.UnixToTime: _unix_to_time_sql,
371
371
  exp.Trim: lambda self, e: trim_sql(self, e, default_trim_type="BOTH"),
372
372
  exp.Variance: rename_func("varSamp"),
373
+ exp.VariancePop: rename_func("varPop"),
373
374
  exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),
374
375
  exp.Stddev: rename_func("stddevSamp"),
375
376
  exp.Chr: rename_func("CHAR"),
@@ -2577,11 +2577,10 @@ class DuckDBGenerator(generator.Generator):
2577
2577
  scale = expression.args.get("scale")
2578
2578
 
2579
2579
  if not fmt and precision and scale:
2580
- return self.sql(
2581
- exp.cast(
2582
- expression.this, f"DECIMAL({precision.name}, {scale.name})", dialect="duckdb"
2583
- )
2584
- )
2580
+ to = exp.DataType.build(f"DECIMAL({precision.name}, {scale.name})", dialect="duckdb")
2581
+ if expression.args.get("safe"):
2582
+ return self.sql(exp.TryCast(this=expression.this, to=to))
2583
+ return self.sql(exp.cast(expression.this, to, dialect="duckdb"))
2585
2584
 
2586
2585
  return super().tonumber_sql(expression)
2587
2586
 
@@ -3075,6 +3074,9 @@ class DuckDBGenerator(generator.Generator):
3075
3074
  if milli or nano:
3076
3075
  expression.set("sec", sec)
3077
3076
 
3077
+ if zone := expression.args.get("zone"):
3078
+ return self.sql(exp.AtTimeZone(this=expression, zone=zone.pop()))
3079
+
3078
3080
  return rename_func("MAKE_TIMESTAMP")(self, expression)
3079
3081
 
3080
3082
  @unsupported_args("nano")
@@ -154,9 +154,9 @@ class MySQLGenerator(generator.Generator):
154
154
  exp.DateDiff: remove_ts_or_ds_to_date(
155
155
  lambda self, e: self.func("DATEDIFF", e.this, e.expression), ("this", "expression")
156
156
  ),
157
- exp.DateAdd: remove_ts_or_ds_to_date(date_add_sql("ADD")),
157
+ exp.DateAdd: date_add_sql("ADD"),
158
158
  exp.DateStrToDate: datestrtodate_sql,
159
- exp.DateSub: remove_ts_or_ds_to_date(date_add_sql("SUB")),
159
+ exp.DateSub: date_add_sql("SUB"),
160
160
  exp.DateTrunc: _date_trunc_sql,
161
161
  exp.Day: remove_ts_or_ds_to_date(),
162
162
  exp.DayOfMonth: remove_ts_or_ds_to_date(rename_func("DAYOFMONTH")),
@@ -1529,7 +1529,12 @@ class SingleStoreGenerator(MySQLGenerator):
1529
1529
 
1530
1530
  def collate_sql(self, expression: exp.Collate) -> str:
1531
1531
  # SingleStore does not support setting a collation for column in the SELECT query,
1532
- # so we cast column to a LONGTEXT type with specific collation
1532
+ # so we cast column to a LONGTEXT type with specific collation. If the cast is
1533
+ # already there (e.g. when regenerating previously generated SQL), don't add
1534
+ # another one, so that generation stays idempotent.
1535
+ this = expression.this
1536
+ if isinstance(this, exp.Cast) and this.to.this == exp.DType.LONGTEXT:
1537
+ return self.binary(expression, "COLLATE")
1533
1538
  return self.binary(expression, ":> LONGTEXT COLLATE")
1534
1539
 
1535
1540
  def currentdate_sql(self, expression: exp.CurrentDate) -> str:
@@ -95,9 +95,6 @@ class SparkGenerator(Spark2Generator):
95
95
  exp.ArrayConstructCompact: lambda self, e: self.func(
96
96
  "ARRAY_COMPACT", self.func("ARRAY", *e.expressions)
97
97
  ),
98
- exp.ArrayInsert: lambda self, e: self.func(
99
- "ARRAY_INSERT", e.this, e.args.get("position"), e.expression
100
- ),
101
98
  exp.ArrayAppend: array_append_sql("ARRAY_APPEND"),
102
99
  exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"),
103
100
  exp.BitwiseAndAgg: rename_func("BIT_AND"),
@@ -152,6 +149,9 @@ class SparkGenerator(Spark2Generator):
152
149
  def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
153
150
  return generator.Generator.ignorenulls_sql(self, expression)
154
151
 
152
+ def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
153
+ return super().arrayinsert_sql(expression, index_offset=1)
154
+
155
155
  def bracket_sql(self, expression: exp.Bracket) -> str:
156
156
  if expression.args.get("safe"):
157
157
  key = seq_get(self.bracket_offset_expressions(expression, index_offset=1), 0)
@@ -220,6 +220,16 @@ class Spark2Generator(HiveGenerator):
220
220
 
221
221
  def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
222
222
  arg = expression.this
223
+ if (
224
+ isinstance(arg, exp.Literal)
225
+ and arg.is_number
226
+ and expression.to.is_type(exp.DType.DECIMAL)
227
+ and len(expression.to.expressions) == 2
228
+ and isinstance(expression.to.expressions[1].this, exp.Neg)
229
+ ):
230
+ # Spark 2 supports negative scales in literals, but not in DECIMAL type syntax.
231
+ return f"{self.sql(arg)}BD"
232
+
223
233
  is_json_extract = isinstance(
224
234
  arg, (exp.JSONExtract, exp.JSONExtractScalar)
225
235
  ) and not arg.args.get("variant_extract")
@@ -20,7 +20,7 @@ from sqlglot.tokens import TokenType
20
20
 
21
21
 
22
22
  def _transform_create(expression: exp.Expr) -> exp.Expr:
23
- """Move primary key to a column and enforce auto_increment on primary keys."""
23
+ """Move primary key to a column and place auto_increment after it."""
24
24
  schema = expression.this
25
25
 
26
26
  if isinstance(expression, exp.Create) and isinstance(schema, exp.Schema):
@@ -52,12 +52,9 @@ def _transform_create(expression: exp.Expr) -> exp.Expr:
52
52
  auto_increment = constraint
53
53
  auto_increment_index = i
54
54
 
55
- if auto_increment is not None and (
56
- primary_key_index == -1 or auto_increment_index < primary_key_index
57
- ):
55
+ if auto_increment is not None and 0 <= auto_increment_index < primary_key_index:
58
56
  column.constraints.remove(auto_increment)
59
- if primary_key_index != -1:
60
- column.constraints.insert(primary_key_index, auto_increment)
57
+ column.constraints.insert(primary_key_index, auto_increment)
61
58
 
62
59
  return expression
63
60
 
@@ -206,6 +203,16 @@ class SQLiteGenerator(generator.Generator):
206
203
 
207
204
  LIMIT_FETCH = "LIMIT"
208
205
 
206
+ def autoincrementcolumnconstraint_sql(
207
+ self, expression: exp.AutoIncrementColumnConstraint
208
+ ) -> str:
209
+ column = expression.find_ancestor(exp.ColumnDef)
210
+ if column and column.find(exp.PrimaryKeyColumnConstraint):
211
+ return super().autoincrementcolumnconstraint_sql(expression)
212
+
213
+ self.unsupported("SQLite AUTOINCREMENT requires an INTEGER PRIMARY KEY")
214
+ return ""
215
+
209
216
  def insert_sql(self, expression: exp.Insert) -> str:
210
217
  if expression.args.get("ignore"):
211
218
  expression.set("ignore", False)
@@ -12,6 +12,7 @@ from sqlglot.dialects.dialect import (
12
12
  property_sql,
13
13
  var_map_sql,
14
14
  )
15
+ from sqlglot.generator import Generator
15
16
  from sqlglot.generators.mysql import MySQLGenerator
16
17
 
17
18
 
@@ -292,6 +293,24 @@ class StarRocksGenerator(MySQLGenerator):
292
293
  "with",
293
294
  }
294
295
 
296
+ def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
297
+ # IGNORE NULLS follows the first argument, e.g. LAG(x IGNORE NULLS, 1, 0)
298
+ # https://docs.starrocks.io/docs/sql-reference/sql-functions/Window_function/
299
+ func = expression.this
300
+ if isinstance(func, (exp.FirstValue, exp.LastValue, exp.Lag, exp.Lead)):
301
+ return self.func(
302
+ func.sql_name(),
303
+ Generator.ignorenulls_sql(self, exp.IgnoreNulls(this=func.this)),
304
+ func.args.get("offset"),
305
+ func.args.get("default"),
306
+ )
307
+
308
+ return super().ignorenulls_sql(expression)
309
+
310
+ def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
311
+ # RESPECT NULLS is the default behavior, and StarRocks rejects the keyword
312
+ return self.sql(expression, "this")
313
+
295
314
  def create_sql(self, expression: exp.Create) -> str:
296
315
  # Starrocks' primary key is defined outside of the schema, so we need to move it there
297
316
  schema = expression.this
@@ -205,10 +205,12 @@ def canonicalize_internal_names(expression: E) -> E:
205
205
  ],
206
206
  )
207
207
 
208
- # BigQuery UNNEST ... WITH OFFSET AS <id> declares a pseudo-column via
209
- # the offset arg (not the alias).
210
- if isinstance(alias_holder, exp.Unnest):
211
- offset_id = alias_holder.args.get("offset")
208
+ # UNNEST stores its offset column outside the alias, including under LATERAL.
209
+ unnest = (
210
+ alias_holder.this if isinstance(alias_holder, exp.Lateral) else alias_holder
211
+ )
212
+ if isinstance(unnest, exp.Unnest):
213
+ offset_id = unnest.args.get("offset")
212
214
  if isinstance(offset_id, exp.Identifier) and offset_id.name in name_map:
213
215
  _canon(offset_id, name_map[offset_id.name])
214
216
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import itertools
4
4
  import re
5
5
  import typing as t
6
+ from collections import Counter
6
7
 
7
8
  from sqlglot import alias, exp
8
9
  from sqlglot.dialects.dialect import Dialect, DialectType
@@ -492,6 +493,8 @@ def _expand_order_by_and_distinct_on(scope: Scope, resolver: Resolver) -> None:
492
493
  if not isinstance(expression, exp.Selectable):
493
494
  return
494
495
 
496
+ alias_names = {name for name, count in Counter(expression.named_selects).items() if count == 1}
497
+
495
498
  # TODO (mypyc): rebind to exp.Expr to avoid Selectable trait vtable dispatch for .args
496
499
  expr: exp.Expr = expression
497
500
 
@@ -510,7 +513,7 @@ def _expand_order_by_and_distinct_on(scope: Scope, resolver: Resolver) -> None:
510
513
  for original, expanded in zip(
511
514
  modifier_expressions,
512
515
  _expand_positional_references(
513
- scope, modifier_expressions, resolver.dialect, alias=True
516
+ scope, modifier_expressions, resolver.dialect, alias_names=alias_names
514
517
  ),
515
518
  ):
516
519
  for agg in original.find_all(exp.AggFunc):
@@ -521,18 +524,23 @@ def _expand_order_by_and_distinct_on(scope: Scope, resolver: Resolver) -> None:
521
524
  original.replace(expanded)
522
525
 
523
526
  if expr.args.get("group"):
524
- selects = {s.this: exp.column(s.alias_or_name) for s in expression.selects}
527
+ selects = {
528
+ s.this: exp.column(s.alias_or_name)
529
+ for s in expression.selects
530
+ if s.alias_or_name in alias_names
531
+ }
525
532
 
526
533
  for node in modifier_expressions:
527
- node.replace(
528
- exp.to_identifier(_select_by_pos(expression, node).alias)
529
- if node.is_int
530
- else selects.get(node, node)
531
- )
534
+ # Remaining ordinals reference duplicate aliases and must stay positional.
535
+ if not node.is_int:
536
+ node.replace(selects.get(node, node))
532
537
 
533
538
 
534
539
  def _expand_positional_references(
535
- scope: Scope, expressions: Iterable[exp.Expr], dialect: Dialect, alias: bool = False
540
+ scope: Scope,
541
+ expressions: Iterable[exp.Expr],
542
+ dialect: Dialect,
543
+ alias_names: set[str] | None = None,
536
544
  ) -> list[exp.Expr]:
537
545
  new_nodes: list[exp.Expr] = []
538
546
  ambiguous_projections = None
@@ -546,8 +554,10 @@ def _expand_positional_references(
546
554
  if node.is_int and isinstance(node, exp.Literal):
547
555
  select = _select_by_pos(expression, node)
548
556
 
549
- if alias:
550
- new_nodes.append(exp.column(select.args["alias"].copy()))
557
+ if alias_names is not None:
558
+ new_nodes.append(
559
+ exp.column(select.args["alias"].copy()) if select.alias in alias_names else node
560
+ )
551
561
  else:
552
562
  # TODO (mypyc): use a separate variable to avoid reusing `select` (Alias) with a different type
553
563
  select_expr: exp.Expr = select.this
@@ -606,11 +616,25 @@ def _convert_columns_to_dots(scope: Scope, resolver: Resolver) -> None:
606
616
  is_star = isinstance(column.this, exp.Star)
607
617
  column_table: str | exp.Identifier | None = column.table
608
618
  dot_parts = column.meta.pop("dot_parts", [])
619
+ root_resolver: Resolver | None = None
620
+ if not is_star and column.db and column_table in scope.selected_sources:
621
+ root, field = column.parts[:2]
622
+ for candidate_resolver in itertools.chain((resolver,), resolver.outer_resolvers()):
623
+ if root.name not in candidate_resolver.scope.selected_sources:
624
+ continue
625
+ if field.name in candidate_resolver.get_source_columns(root.name):
626
+ root_resolver = candidate_resolver
627
+ break
609
628
  if (
610
629
  column_table
611
- and (column_table not in scope.selected_sources or (is_star and column.db))
612
630
  and (
613
- is_star
631
+ column_table not in scope.selected_sources
632
+ or (is_star and column.db)
633
+ or root_resolver
634
+ )
635
+ and (
636
+ root_resolver
637
+ or is_star
614
638
  or not scope.parent
615
639
  or column_table not in scope.parent.sources
616
640
  or not scope.is_correlated_subquery
@@ -619,10 +643,11 @@ def _convert_columns_to_dots(scope: Scope, resolver: Resolver) -> None:
619
643
  root, *parts = column.parts
620
644
  was_qualified = False
621
645
 
622
- # Unlike columns, correlated stars can't be deferred to the outer scopes, since they
623
- # must be expanded in this one, so they're resolved against those scopes as well
646
+ # Correlated stars cannot be deferred; qualified paths use their root source's resolver.
624
647
  resolvers: t.Iterable[Resolver] = (
625
- itertools.chain((resolver,), resolver.outer_resolvers()) if is_star else (resolver,)
648
+ itertools.chain((resolver,), resolver.outer_resolvers())
649
+ if is_star
650
+ else (root_resolver or resolver,)
626
651
  )
627
652
  for source_resolver in resolvers:
628
653
  selected_sources = source_resolver.scope.selected_sources
@@ -43,22 +43,26 @@ class Resolver:
43
43
  """
44
44
  column_name = column if isinstance(column, str) else column.name
45
45
 
46
- table_name = self._get_table_name_from_sources(column_name)
46
+ join_context = (
47
+ self._get_column_join_context(column) if isinstance(column, exp.Column) else None
48
+ )
49
+ is_semi_or_anti_join = join_context is not None and join_context.is_semi_or_anti_join
50
+
51
+ # A SEMI/ANTI join's own table is excluded from the global lookup below, so skip it here
52
+ table_name = (
53
+ None if is_semi_or_anti_join else self._get_table_name_from_sources(column_name)
54
+ )
47
55
 
48
- if not table_name and isinstance(column, exp.Column):
56
+ if not table_name and join_context is not None:
49
57
  # Fall-back case: If we couldn't find the `table_name` from ALL of the sources,
50
58
  # attempt to disambiguate the column based on other characteristics e.g if this column is in a join condition,
51
59
  # we may be able to disambiguate based on the source order.
52
- if join_context := self._get_column_join_context(column):
53
- # In this case, the return value will be the join that _may_ be able to disambiguate the column
54
- # and we can use the source columns available at that join to get the table name
55
- # catch OptimizeError if column is still ambiguous and try to resolve with schema inference below
56
- try:
57
- table_name = self._get_table_name_from_sources(
58
- column_name, self._get_available_source_columns(join_context)
59
- )
60
- except OptimizeError:
61
- pass
60
+ try:
61
+ table_name = self._get_table_name_from_sources(
62
+ column_name, self._get_available_source_columns(join_context)
63
+ )
64
+ except OptimizeError:
65
+ pass
62
66
 
63
67
  if not table_name and self._infer_schema:
64
68
  sources_without_schema = tuple(
@@ -707,7 +707,7 @@ class Simplifier:
707
707
  joins.append(node)
708
708
 
709
709
  for where in wheres:
710
- if always_true(where.this):
710
+ if always_true(where.this) and not isinstance(where.parent, exp.Filter):
711
711
  where.pop()
712
712
  for join in joins:
713
713
  # Only an inner join can become a CROSS JOIN: a cross join is empty as soon as either
@@ -841,7 +841,10 @@ class Simplifier:
841
841
  if complement_subquery_predicate:
842
842
  right = complement_subquery_predicate(this=right.this)
843
843
 
844
- return self.COMPLEMENT_COMPARISONS[this.__class__](this=this.this, expression=right)
844
+ return exp.paren(
845
+ self.COMPLEMENT_COMPARISONS[this.__class__](this=this.this, expression=right),
846
+ copy=False,
847
+ )
845
848
  if isinstance(this, exp.Paren):
846
849
  condition = this.unnest()
847
850
  if isinstance(condition, exp.And):
@@ -1633,7 +1633,9 @@ class Parser:
1633
1633
  SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
1634
1634
 
1635
1635
  TYPE_LITERAL_PARSERS: t.ClassVar = {
1636
- exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)),
1636
+ exp.DType.JSON: lambda self, this, _: self.expression(
1637
+ exp.ParseJSON(this=this, is_literal=True)
1638
+ ),
1637
1639
  }
1638
1640
 
1639
1641
  TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
@@ -3693,7 +3695,7 @@ class Parser:
3693
3695
  settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
3694
3696
  default=self._match_text_seq("DEFAULT", "VALUES"),
3695
3697
  expression=set_values
3696
- or self._parse_derived_table_values()
3698
+ or self._parse_derived_table_values(allow_value_synonym=True)
3697
3699
  or self._parse_ddl_select(),
3698
3700
  conflict=self._parse_on_conflict(),
3699
3701
  returning=returning or self._parse_returning(),
@@ -3738,7 +3740,7 @@ class Parser:
3738
3740
  action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
3739
3741
  if self._prev.token_type == TokenType.UPDATE:
3740
3742
  self._match(TokenType.SET)
3741
- expressions = self._parse_csv(self._parse_equality)
3743
+ expressions = self._parse_csv(self._parse_update_assignment)
3742
3744
  else:
3743
3745
  expressions = None
3744
3746
 
@@ -3867,6 +3869,15 @@ class Parser:
3867
3869
  )
3868
3870
  )
3869
3871
 
3872
+ def _parse_update_assignment(self) -> exp.Expr | None:
3873
+ this = self._parse_comparison()
3874
+ if self._match(TokenType.EQ):
3875
+ comments = self._prev_comments
3876
+ this = self.expression(
3877
+ exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments
3878
+ )
3879
+ return this
3880
+
3870
3881
  def _parse_update(self) -> exp.Update:
3871
3882
  hint = self._parse_hint()
3872
3883
  kwargs: dict[str, object] = {
@@ -3875,7 +3886,7 @@ class Parser:
3875
3886
  }
3876
3887
  while self._curr:
3877
3888
  if self._match(TokenType.SET):
3878
- kwargs["expressions"] = self._parse_csv(self._parse_equality)
3889
+ kwargs["expressions"] = self._parse_csv(self._parse_update_assignment)
3879
3890
  elif self._match(TokenType.RETURNING, advance=False):
3880
3891
  kwargs["returning"] = self._parse_returning()
3881
3892
  elif self._match(TokenType.FROM, advance=False):
@@ -5259,11 +5270,14 @@ class Parser:
5259
5270
 
5260
5271
  return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
5261
5272
 
5262
- def _parse_derived_table_values(self) -> exp.Values | None:
5273
+ def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None:
5263
5274
  is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
5264
5275
  if not is_derived and not (
5265
5276
  # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
5266
- self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES")
5277
+ self._match_text_seq("VALUES")
5278
+ or self._match_text_seq("FORMAT", "VALUES")
5279
+ # MySQL accepts VALUE as a synonym for VALUES in INSERT statements.
5280
+ or (allow_value_synonym and self._match_text_seq("VALUE"))
5267
5281
  ):
5268
5282
  return None
5269
5283
 
@@ -5766,7 +5780,7 @@ class Parser:
5766
5780
 
5767
5781
  def _parse_limit_options(self) -> exp.LimitOptions | None:
5768
5782
  percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
5769
- rows = self._match_set((TokenType.ROW, TokenType.ROWS))
5783
+ rows = self._match_texts(("ROW", "ROWS"))
5770
5784
  self._match_text_seq("ONLY")
5771
5785
  with_ties = self._match_text_seq("WITH", "TIES")
5772
5786
 
@@ -5830,7 +5844,11 @@ class Parser:
5830
5844
  else "FIRST"
5831
5845
  )
5832
5846
 
5833
- count = self._parse_field(tokens=self.FETCH_TOKENS)
5847
+ count = (
5848
+ None
5849
+ if self._match_texts(("ROW", "ROWS"), advance=False)
5850
+ else self._parse_field(tokens=self.FETCH_TOKENS)
5851
+ )
5834
5852
 
5835
5853
  return self.expression(
5836
5854
  exp.Fetch(
@@ -5845,7 +5863,7 @@ class Parser:
5845
5863
  return this
5846
5864
 
5847
5865
  count = self._parse_term()
5848
- self._match_set((TokenType.ROW, TokenType.ROWS))
5866
+ self._match_texts(("ROW", "ROWS"))
5849
5867
 
5850
5868
  return self.expression(
5851
5869
  exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
@@ -7782,10 +7800,11 @@ class Parser:
7782
7800
  and self._next.text.upper() in self.PROCEDURE_OPTIONS
7783
7801
  )
7784
7802
 
7803
+ index = self._index
7785
7804
  if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
7786
7805
  constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
7787
7806
  if not constraint:
7788
- self._retreat(self._index - 1)
7807
+ self._retreat(index)
7789
7808
  return None
7790
7809
 
7791
7810
  return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
@@ -9577,7 +9596,7 @@ class Parser:
9577
9596
  then = self.expression(
9578
9597
  exp.Update(
9579
9598
  expressions=self._match(TokenType.SET)
9580
- and self._parse_csv(self._parse_equality),
9599
+ and self._parse_csv(self._parse_update_assignment),
9581
9600
  where=self._parse_where(),
9582
9601
  )
9583
9602
  )