sqlglotc 30.15.0__tar.gz → 30.16.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.15.0/sqlglotc.egg-info → sqlglotc-30.16.0}/PKG-INFO +2 -2
  2. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/errors.py +15 -1
  3. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/array.py +6 -4
  4. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/core.py +20 -14
  5. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/ddl.py +1 -0
  6. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/json.py +4 -3
  7. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/query.py +4 -0
  8. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/string.py +3 -3
  9. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generator.py +57 -14
  10. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/bigquery.py +1 -1
  11. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/clickhouse.py +3 -2
  12. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/doris.py +5 -2
  13. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/duckdb.py +24 -22
  14. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/hive.py +7 -3
  15. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/mysql.py +11 -6
  16. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/postgres.py +8 -1
  17. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/singlestore.py +2 -1
  18. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/snowflake.py +1 -1
  19. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/spark2.py +5 -1
  20. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/sqlite.py +7 -1
  21. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/starrocks.py +3 -2
  22. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/trino.py +34 -0
  23. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/tsql.py +1 -0
  24. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/lineage.py +78 -33
  25. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/qualify_columns.py +14 -0
  26. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/qualify_tables.py +9 -1
  27. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/scope.py +6 -0
  28. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/simplify.py +2 -1
  29. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parser.py +95 -19
  30. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/clickhouse.py +24 -15
  31. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/duckdb.py +11 -0
  32. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/mysql.py +1 -1
  33. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/postgres.py +16 -1
  34. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/prql.py +1 -1
  35. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/snowflake.py +11 -4
  36. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/sqlite.py +79 -0
  37. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/teradata.py +1 -1
  38. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/trino.py +73 -9
  39. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/tsql.py +8 -2
  40. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/tokenizer_core.py +1 -1
  41. {sqlglotc-30.15.0 → sqlglotc-30.16.0/sqlglotc.egg-info}/PKG-INFO +2 -2
  42. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglotc.egg-info/requires.txt +1 -1
  43. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglotc.egg-info/scm_file_list.json +2 -2
  44. sqlglotc-30.16.0/sqlglotc.egg-info/scm_version.json +8 -0
  45. sqlglotc-30.15.0/sqlglotc.egg-info/scm_version.json +0 -8
  46. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/MANIFEST.in +0 -0
  47. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/pyproject.toml +0 -0
  48. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/setup.cfg +0 -0
  49. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/setup.py +0 -0
  50. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/anonymize.py +0 -0
  51. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/executor/table.py +0 -0
  52. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/aggregate.py +0 -0
  53. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/builders.py +0 -0
  54. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/constraints.py +0 -0
  55. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/datatypes.py +0 -0
  56. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/dml.py +0 -0
  57. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/functions.py +0 -0
  58. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/math.py +0 -0
  59. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/properties.py +0 -0
  60. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/expressions/temporal.py +0 -0
  61. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/athena.py +0 -0
  62. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/databricks.py +0 -0
  63. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/dax.py +0 -0
  64. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/dremio.py +0 -0
  65. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/drill.py +0 -0
  66. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/druid.py +0 -0
  67. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/dune.py +0 -0
  68. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/exasol.py +0 -0
  69. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/fabric.py +0 -0
  70. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/materialize.py +0 -0
  71. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/oracle.py +0 -0
  72. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/presto.py +0 -0
  73. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/prql.py +0 -0
  74. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/python.py +0 -0
  75. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/redshift.py +0 -0
  76. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/risingwave.py +0 -0
  77. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/solr.py +0 -0
  78. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/spark.py +0 -0
  79. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/tableau.py +0 -0
  80. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/generators/teradata.py +0 -0
  81. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/helper.py +0 -0
  82. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/annotate_types.py +0 -0
  83. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/canonicalize_internal_names.py +0 -0
  84. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/isolate_table_selects.py +0 -0
  85. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/normalize_identifiers.py +0 -0
  86. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/qualify.py +0 -0
  87. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/optimizer/resolver.py +0 -0
  88. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/athena.py +0 -0
  89. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/base.py +0 -0
  90. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/bigquery.py +0 -0
  91. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/databricks.py +0 -0
  92. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/dax.py +0 -0
  93. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/doris.py +0 -0
  94. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/dremio.py +0 -0
  95. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/drill.py +0 -0
  96. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/druid.py +0 -0
  97. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/dune.py +0 -0
  98. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/exasol.py +0 -0
  99. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/fabric.py +0 -0
  100. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/hive.py +0 -0
  101. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/materialize.py +0 -0
  102. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/oracle.py +0 -0
  103. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/presto.py +0 -0
  104. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/redshift.py +0 -0
  105. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/risingwave.py +0 -0
  106. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/singlestore.py +0 -0
  107. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/solr.py +0 -0
  108. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/spark.py +0 -0
  109. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/spark2.py +0 -0
  110. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/starrocks.py +0 -0
  111. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/parsers/tableau.py +0 -0
  112. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/schema.py +0 -0
  113. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/serde.py +0 -0
  114. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/time.py +0 -0
  115. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglot/trie.py +0 -0
  116. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglotc.egg-info/SOURCES.txt +0 -0
  117. {sqlglotc-30.15.0 → sqlglotc-30.16.0}/sqlglotc.egg-info/dependency_links.txt +0 -0
  118. {sqlglotc-30.15.0 → sqlglotc-30.16.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.15.0
3
+ Version: 30.16.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.15.0
10
+ Requires-Dist: sqlglot==30.16.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: setuptools>=61.0; extra == "dev"
13
13
  Requires-Dist: setuptools_scm; extra == "dev"
@@ -72,7 +72,21 @@ class ParseError(SqlglotError):
72
72
 
73
73
 
74
74
  class TokenError(SqlglotError):
75
- pass
75
+ """Error raised when tokenizing fails.
76
+
77
+ When available, `start` and `end` are the offsets in the source SQL of the context
78
+ snippet quoted in the message, i.e. the snippet is `sql[start:end]`.
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ message: str,
84
+ start: int | None = None,
85
+ end: int | None = None,
86
+ ):
87
+ super().__init__(message)
88
+ self.start = start
89
+ self.end = end
76
90
 
77
91
 
78
92
  class OptimizeError(SqlglotError):
@@ -8,6 +8,7 @@ from sqlglot.expressions.core import (
8
8
  Expr,
9
9
  Func,
10
10
  Binary,
11
+ Predicate,
11
12
  to_identifier,
12
13
  )
13
14
  from sqlglot.helper import trait
@@ -109,16 +110,16 @@ class ArrayAny(Expression, Func):
109
110
  arg_types = {"this": True, "expression": True}
110
111
 
111
112
 
112
- class ArrayContains(Expression, Binary, Func):
113
+ class ArrayContains(Expression, Binary, Predicate, Func):
113
114
  arg_types = {"this": True, "expression": True, "ensure_variant": False, "check_null": False}
114
115
  _sql_names = ["ARRAY_CONTAINS", "ARRAY_HAS"]
115
116
 
116
117
 
117
- class ArrayContainsAll(Expression, Binary, Func):
118
+ class ArrayContainsAll(Expression, Binary, Predicate, Func):
118
119
  _sql_names = ["ARRAY_CONTAINS_ALL", "ARRAY_HAS_ALL"]
119
120
 
120
121
 
121
- class ArrayContainedBy(Expression, Binary, Func):
122
+ class ArrayContainedBy(Expression, Binary, Predicate, Func):
122
123
  pass
123
124
 
124
125
 
@@ -132,7 +133,7 @@ class ArrayIntersect(Expression, Func):
132
133
  _sql_names = ["ARRAY_INTERSECT", "ARRAY_INTERSECTION"]
133
134
 
134
135
 
135
- class ArrayOverlaps(Expression, Binary, Func):
136
+ class ArrayOverlaps(Expression, Binary, Predicate, Func):
136
137
  arg_types = {"this": True, "expression": True, "null_safe": False}
137
138
 
138
139
 
@@ -343,6 +344,7 @@ class ToMap(Expression, Func):
343
344
  class VarMap(Expression, Func):
344
345
  arg_types = {"keys": True, "values": True}
345
346
  is_var_len_args = True
347
+ var_len_arg_key = "values"
346
348
 
347
349
  @property
348
350
  def keys(self) -> list[Expr]:
@@ -14,7 +14,7 @@ from builtins import type as Type
14
14
  from collections import deque
15
15
  from collections.abc import Collection, Iterator, Mapping, MutableMapping, Sequence
16
16
  from copy import deepcopy
17
- from decimal import Decimal
17
+ from decimal import Decimal, InvalidOperation
18
18
  from functools import reduce
19
19
 
20
20
  from sqlglot._typing import E, GeneratorNoDialectArgs, ParserNoDialectArgs, T
@@ -85,6 +85,7 @@ class Expr:
85
85
  arg_types: t.ClassVar[dict[str, bool]] = {"this": True}
86
86
  required_args: t.ClassVar[set[str]] = {"this"}
87
87
  is_var_len_args: t.ClassVar[bool] = False
88
+ var_len_arg_key: t.ClassVar[str] = "expressions"
88
89
  _hash_raw_args: t.ClassVar[bool] = False
89
90
  is_subquery: t.ClassVar[bool] = False
90
91
  is_cast: t.ClassVar[bool] = False
@@ -1577,7 +1578,7 @@ class Condition(Expr):
1577
1578
 
1578
1579
  @trait
1579
1580
  class Predicate(Condition):
1580
- """Relationships like x = y, x > 1, x >= y."""
1581
+ """Any condition that evaluates to a boolean, e.g. x = y, x LIKE 'a%', a @> b."""
1581
1582
 
1582
1583
 
1583
1584
  class Cache(Expression):
@@ -1643,8 +1644,11 @@ class Func(Condition):
1643
1644
  The base class for all function expressions.
1644
1645
 
1645
1646
  Attributes:
1646
- is_var_len_args (bool): if set to True the last argument defined in arg_types will be
1647
+ is_var_len_args (bool): if set to True the argument identified by var_len_arg_key will be
1647
1648
  treated as a variable length argument and the argument's value will be stored as a list.
1649
+ var_len_arg_key (str): the arg_types key that collects the variable length arguments.
1650
+ Arguments preceding it in arg_types are filled positionally; those following it (e.g.
1651
+ dialect flags) are never populated by from_arg_list.
1648
1652
  _sql_names (list): the SQL name (1st item in the list) and aliases (subsequent items) for this
1649
1653
  function expression. These values are used to map this node to a name during parsing as
1650
1654
  well as to provide the function's name during SQL string generation. By default the SQL
@@ -1652,18 +1656,17 @@ class Func(Condition):
1652
1656
  """
1653
1657
 
1654
1658
  is_var_len_args: t.ClassVar[bool] = False
1659
+ var_len_arg_key: t.ClassVar[str] = "expressions"
1655
1660
  _sql_names: t.ClassVar[list[str]] = []
1656
1661
 
1657
1662
  @classmethod
1658
1663
  def from_arg_list(cls, args: Sequence[object]) -> Self:
1659
1664
  if cls.is_var_len_args:
1660
1665
  all_arg_keys = tuple(cls.arg_types)
1661
- # If this function supports variable length argument treat the last argument as such.
1662
- non_var_len_arg_keys = all_arg_keys[:-1] if cls.is_var_len_args else all_arg_keys
1663
- num_non_var = len(non_var_len_arg_keys)
1666
+ var_len_index = all_arg_keys.index(cls.var_len_arg_key)
1664
1667
 
1665
- args_dict = {arg_key: arg for arg, arg_key in zip(args, non_var_len_arg_keys)}
1666
- args_dict[all_arg_keys[-1]] = args[num_non_var:]
1668
+ args_dict = {arg_key: arg for arg, arg_key in zip(args, all_arg_keys[:var_len_index])}
1669
+ args_dict[cls.var_len_arg_key] = args[var_len_index:]
1667
1670
  else:
1668
1671
  args_dict = {arg_key: arg for arg, arg_key in zip(args, cls.arg_types)}
1669
1672
 
@@ -1764,7 +1767,10 @@ class Literal(Expression, Condition):
1764
1767
  try:
1765
1768
  return int(self.this)
1766
1769
  except ValueError:
1767
- return Decimal(self.this)
1770
+ try:
1771
+ return Decimal(self.this)
1772
+ except InvalidOperation as e:
1773
+ raise ValueError(f"Invalid numeric literal: {self.this!r}") from e
1768
1774
  return self.this
1769
1775
 
1770
1776
 
@@ -2119,15 +2125,15 @@ class Div(Expression, Binary):
2119
2125
  arg_types = {"this": True, "expression": True, "typed": False, "safe": False}
2120
2126
 
2121
2127
 
2122
- class Overlaps(Expression, Binary):
2128
+ class Overlaps(Expression, Binary, Predicate):
2123
2129
  pass
2124
2130
 
2125
2131
 
2126
- class ExtendsLeft(Expression, Binary):
2132
+ class ExtendsLeft(Expression, Binary, Predicate):
2127
2133
  pass
2128
2134
 
2129
2135
 
2130
- class ExtendsRight(Expression, Binary):
2136
+ class ExtendsRight(Expression, Binary, Predicate):
2131
2137
  pass
2132
2138
 
2133
2139
 
@@ -2231,7 +2237,7 @@ class Sub(Expression, Binary):
2231
2237
  pass
2232
2238
 
2233
2239
 
2234
- class Adjacent(Expression, Binary):
2240
+ class Adjacent(Expression, Binary, Predicate):
2235
2241
  pass
2236
2242
 
2237
2243
 
@@ -2317,7 +2323,7 @@ class Pow(Expression, Binary, Func):
2317
2323
  _sql_names = ["POWER", "POW"]
2318
2324
 
2319
2325
 
2320
- class RegexpLike(Expression, Binary, Func):
2326
+ class RegexpLike(Expression, Binary, Predicate, Func):
2321
2327
  arg_types = {"this": True, "expression": True, "flag": False, "full_match": False}
2322
2328
 
2323
2329
 
@@ -250,6 +250,7 @@ class AlterColumn(Expression):
250
250
  "allow_null": False,
251
251
  "visible": False,
252
252
  "rename_to": False,
253
+ "exists": False,
253
254
  }
254
255
 
255
256
 
@@ -46,15 +46,15 @@ class JSONArrayInsert(Expression, Func):
46
46
  _sql_names = ["JSON_ARRAY_INSERT"]
47
47
 
48
48
 
49
- class JSONBContains(Expression, Binary, Func):
49
+ class JSONBContains(Expression, Binary, Predicate, Func):
50
50
  _sql_names = ["JSONB_CONTAINS"]
51
51
 
52
52
 
53
- class JSONBContainsAllTopKeys(Expression, Binary, Func):
53
+ class JSONBContainsAllTopKeys(Expression, Binary, Predicate, Func):
54
54
  pass
55
55
 
56
56
 
57
- class JSONBContainsAnyTopKeys(Expression, Binary, Func):
57
+ class JSONBContainsAnyTopKeys(Expression, Binary, Predicate, Func):
58
58
  pass
59
59
 
60
60
 
@@ -133,6 +133,7 @@ class JSONExtractScalar(Expression, Binary, Func):
133
133
  "expressions": False,
134
134
  "json_type": False,
135
135
  "scalar_only": False,
136
+ "json_subtype": False,
136
137
  }
137
138
  _sql_names = ["JSON_EXTRACT_SCALAR"]
138
139
  is_var_len_args = True
@@ -2119,6 +2119,10 @@ class IfBlock(Expression):
2119
2119
  arg_types = {"this": True, "true": True, "false": False}
2120
2120
 
2121
2121
 
2122
+ class CaseStatement(Expression):
2123
+ arg_types = {"this": False, "ifs": True, "default": False}
2124
+
2125
+
2122
2126
  class WhileBlock(Expression):
2123
2127
  arg_types = {"this": True, "body": True}
2124
2128
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from sqlglot.expressions.core import Expression, Func, Binary
5
+ from sqlglot.expressions.core import Expression, Func, Binary, Predicate
6
6
 
7
7
 
8
8
  # String basics
@@ -445,11 +445,11 @@ class RegexpExtractAll(Expression, Func):
445
445
  }
446
446
 
447
447
 
448
- class RegexpFullMatch(Expression, Binary, Func):
448
+ class RegexpFullMatch(Expression, Binary, Predicate, Func):
449
449
  arg_types = {"this": True, "expression": True, "options": False}
450
450
 
451
451
 
452
- class RegexpILike(Expression, Binary, Func):
452
+ class RegexpILike(Expression, Binary, Predicate, Func):
453
453
  arg_types = {"this": True, "expression": True, "flag": False}
454
454
 
455
455
 
@@ -4,6 +4,7 @@ import logging
4
4
  import re
5
5
  import typing as t
6
6
  from collections import defaultdict
7
+ from decimal import Decimal
7
8
  from functools import reduce, wraps
8
9
 
9
10
  from sqlglot import exp
@@ -484,6 +485,12 @@ class Generator:
484
485
  # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
485
486
  SUPPORTS_CHANGE_COLUMN = False
486
487
 
488
+ # Whether ALTER COLUMN can set a column's nullability together with its type
489
+ SUPPORTS_ALTER_COLUMN_NULLABILITY = False
490
+
491
+ # Whether ALTER COLUMN IF EXISTS is supported
492
+ SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
493
+
487
494
  # Whether the LikeProperty needs to be specified inside of the schema clause
488
495
  LIKE_PROPERTY_INSIDE_SCHEMA = False
489
496
 
@@ -1642,10 +1649,11 @@ class Generator:
1642
1649
  def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1643
1650
  this = self.sql(expression, "this")
1644
1651
  escape = expression.args.get("escape")
1652
+ unicode_start = self.dialect.UNICODE_START
1645
1653
 
1646
- if self.dialect.UNICODE_START:
1654
+ if unicode_start:
1647
1655
  escape_substitute = r"\\\1"
1648
- left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1656
+ left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1649
1657
  else:
1650
1658
  escape_substitute = r"\\u\1"
1651
1659
  left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
@@ -1657,9 +1665,16 @@ class Generator:
1657
1665
  escape_pattern = ESCAPED_UNICODE_RE
1658
1666
  escape_sql = ""
1659
1667
 
1660
- if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1668
+ if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1661
1669
  this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1662
1670
 
1671
+ if unicode_start:
1672
+ # A Unicode literal only escapes its delimiter by doubling it; the escape character
1673
+ # introduces a code point, so the dialect's ordinary string escapes don't apply here
1674
+ this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1675
+ else:
1676
+ this = self.escape_str(this, escape_backslash=False)
1677
+
1663
1678
  return f"{left_quote}{this}{right_quote}{escape_sql}"
1664
1679
 
1665
1680
  def rawstring_sql(self, expression: exp.RawString) -> str:
@@ -1702,11 +1717,12 @@ class Generator:
1702
1717
  continue
1703
1718
 
1704
1719
  param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1705
- if (
1706
- isinstance(param_value, exp.Literal)
1707
- and param_value.is_number
1708
- and int(param_value.to_py()) > bound
1709
- ):
1720
+ value = (
1721
+ param_value.to_py()
1722
+ if isinstance(param_value, exp.Literal) and param_value.is_number
1723
+ else None
1724
+ )
1725
+ if isinstance(value, (int, Decimal)) and value > bound:
1710
1726
  self.unsupported(
1711
1727
  f"{type_value.value} parameter {param_value.name} exceeds "
1712
1728
  f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
@@ -4198,6 +4214,13 @@ class Generator:
4198
4214
  def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4199
4215
  this = self.sql(expression, "this")
4200
4216
 
4217
+ exists = ""
4218
+ if expression.args.get("exists"):
4219
+ if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4220
+ exists = " IF EXISTS"
4221
+ else:
4222
+ self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4223
+
4201
4224
  dtype = self.sql(expression, "dtype")
4202
4225
  if dtype:
4203
4226
  collate = self.sql(expression, "collate")
@@ -4205,19 +4228,24 @@ class Generator:
4205
4228
  using = self.sql(expression, "using")
4206
4229
  using = f" USING {using}" if using else ""
4207
4230
  alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4208
- return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
4231
+ null_constraint = self._alter_column_null_constraint_sql(expression)
4232
+
4233
+ return (
4234
+ f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4235
+ f"{collate}{using}{null_constraint}"
4236
+ )
4209
4237
 
4210
4238
  default = self.sql(expression, "default")
4211
4239
  if default:
4212
- return f"ALTER COLUMN {this} SET DEFAULT {default}"
4240
+ return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4213
4241
 
4214
4242
  comment = self.sql(expression, "comment")
4215
4243
  if comment:
4216
- return f"ALTER COLUMN {this} COMMENT {comment}"
4244
+ return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4217
4245
 
4218
4246
  visible = expression.args.get("visible")
4219
4247
  if visible:
4220
- return f"ALTER COLUMN {this} SET {visible}"
4248
+ return f"ALTER COLUMN{exists} {this} SET {visible}"
4221
4249
 
4222
4250
  allow_null = expression.args.get("allow_null")
4223
4251
  drop = expression.args.get("drop")
@@ -4227,9 +4255,20 @@ class Generator:
4227
4255
 
4228
4256
  if allow_null is not None:
4229
4257
  keyword = "DROP" if drop else "SET"
4230
- return f"ALTER COLUMN {this} {keyword} NOT NULL"
4258
+ return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4259
+
4260
+ return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4261
+
4262
+ def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4263
+ allow_null = expression.args.get("allow_null")
4264
+ if allow_null is None:
4265
+ return ""
4266
+
4267
+ if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4268
+ self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4269
+ return ""
4231
4270
 
4232
- return f"ALTER COLUMN {this} DROP DEFAULT"
4271
+ return " NULL" if allow_null else " NOT NULL"
4233
4272
 
4234
4273
  def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4235
4274
  this = self.sql(expression, "this")
@@ -6272,6 +6311,10 @@ class Generator:
6272
6311
  self.unsupported("Unsupported If block syntax")
6273
6312
  return ""
6274
6313
 
6314
+ def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6315
+ self.unsupported("Unsupported Case statement syntax")
6316
+ return ""
6317
+
6275
6318
  def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6276
6319
  self.unsupported("Unsupported While block syntax")
6277
6320
  return ""
@@ -292,7 +292,7 @@ class BigQueryGenerator(generator.Generator):
292
292
  exp.RowNumber,
293
293
  )
294
294
 
295
- TS_OR_DS_TYPES = (
295
+ TS_OR_DS_TYPES: t.ClassVar = (
296
296
  exp.TsOrDsToDatetime,
297
297
  exp.TsOrDsToTimestamp,
298
298
  exp.TsOrDsToTime,
@@ -175,6 +175,7 @@ class ClickHouseGenerator(generator.Generator):
175
175
  STRUCT_DELIMITER = ("(", ")")
176
176
  NVL2_SUPPORTED = False
177
177
  ALTER_SET_TYPE = "TYPE"
178
+ SUPPORTS_ALTER_COLUMN_IF_EXISTS = True
178
179
  TABLESAMPLE_REQUIRES_PARENS = False
179
180
  TABLESAMPLE_SIZE_IS_ROWS = False
180
181
  TABLESAMPLE_KEYWORDS = "SAMPLE"
@@ -398,7 +399,7 @@ class ClickHouseGenerator(generator.Generator):
398
399
 
399
400
  # There's no list in docs, but it can be found in Clickhouse code
400
401
  # see `ClickHouse/src/Parsers/ParserCreate*.cpp`
401
- ON_CLUSTER_TARGETS = {
402
+ ON_CLUSTER_TARGETS: t.ClassVar = {
402
403
  "SCHEMA", # Transpiled CREATE SCHEMA may have OnCluster property set
403
404
  "DATABASE",
404
405
  "TABLE",
@@ -410,7 +411,7 @@ class ClickHouseGenerator(generator.Generator):
410
411
  }
411
412
 
412
413
  # https://clickhouse.com/docs/en/sql-reference/data-types/nullable
413
- NON_NULLABLE_TYPES = {
414
+ NON_NULLABLE_TYPES: t.ClassVar = {
414
415
  exp.DType.ARRAY,
415
416
  exp.DType.MAP,
416
417
  exp.DType.STRUCT,
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import typing as t
4
+
3
5
  from sqlglot import exp
4
6
  from sqlglot.dialects.dialect import (
5
7
  approx_count_distinct_sql,
@@ -22,6 +24,7 @@ def _lag_lead_sql(self, expression: exp.Lag | exp.Lead) -> str:
22
24
 
23
25
  class DorisGenerator(MySQLGenerator):
24
26
  LAST_DAY_SUPPORTS_DATE_PART = False
27
+ SUPPORTS_ALTER_COLUMN_NULLABILITY = False
25
28
  VARCHAR_REQUIRES_SIZE = False
26
29
  WITH_PROPERTIES_PREFIX = "PROPERTIES"
27
30
  RENAME_TABLE_WITH_DB = False
@@ -41,8 +44,8 @@ class DorisGenerator(MySQLGenerator):
41
44
  exp.BuildProperty: exp.Properties.Location.POST_SCHEMA,
42
45
  }
43
46
 
44
- CAST_MAPPING = {}
45
- TIMESTAMP_FUNC_TYPES = set()
47
+ CAST_MAPPING: t.ClassVar[dict[exp.DType, str]] = {}
48
+ TIMESTAMP_FUNC_TYPES: t.ClassVar[set[exp.DType]] = set()
46
49
 
47
50
  TRANSFORMS = {
48
51
  **MySQLGenerator.TRANSFORMS,
@@ -1951,7 +1951,7 @@ class DuckDBGenerator(generator.Generator):
1951
1951
  IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS: t.ClassVar = _IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS
1952
1952
 
1953
1953
  # Template for ZIPF transpilation - placeholders get replaced with actual parameters
1954
- ZIPF_TEMPLATE: exp.Expr = exp.maybe_parse(
1954
+ ZIPF_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
1955
1955
  """
1956
1956
  WITH rand AS (SELECT :random_expr AS r),
1957
1957
  weights AS (
@@ -1970,22 +1970,24 @@ class DuckDBGenerator(generator.Generator):
1970
1970
 
1971
1971
  # Template for NORMAL transpilation using Box-Muller transform
1972
1972
  # mean + (stddev * sqrt(-2 * ln(u1)) * cos(2 * pi * u2))
1973
- NORMAL_TEMPLATE: exp.Expr = exp.maybe_parse(
1973
+ NORMAL_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
1974
1974
  ":mean + (:stddev * SQRT(-2 * LN(GREATEST(:u1, 1e-10))) * COS(2 * PI() * :u2))"
1975
1975
  )
1976
1976
 
1977
1977
  # Template for generating a seeded pseudo-random value in [0, 1) from a hash
1978
- SEEDED_RANDOM_TEMPLATE: exp.Expr = exp.maybe_parse("(ABS(HASH(:seed)) % 1000000) / 1000000.0")
1978
+ SEEDED_RANDOM_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
1979
+ "(ABS(HASH(:seed)) % 1000000) / 1000000.0"
1980
+ )
1979
1981
 
1980
1982
  # Template for generating signed and unsigned SEQ values within a specified range
1981
- SEQ_UNSIGNED: exp.Expr = _SEQ_UNSIGNED
1982
- SEQ_SIGNED: exp.Expr = _SEQ_SIGNED
1983
+ SEQ_UNSIGNED: t.ClassVar[exp.Expr] = _SEQ_UNSIGNED
1984
+ SEQ_SIGNED: t.ClassVar[exp.Expr] = _SEQ_SIGNED
1983
1985
 
1984
1986
  # Template for MAP_CAT transpilation - Snowflake semantics:
1985
1987
  # 1. Returns NULL if either input is NULL
1986
1988
  # 2. For duplicate keys, prefers non-NULL value (COALESCE(m2[k], m1[k]))
1987
1989
  # 3. Filters out entries with NULL values from the result
1988
- MAPCAT_TEMPLATE: exp.Expr = exp.maybe_parse(
1990
+ MAPCAT_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
1989
1991
  """
1990
1992
  CASE
1991
1993
  WHEN :map1 IS NULL OR :map2 IS NULL THEN NULL
@@ -1999,7 +2001,7 @@ class DuckDBGenerator(generator.Generator):
1999
2001
 
2000
2002
  # Mappings for EXTRACT/DATE_PART transpilation
2001
2003
  # Maps Snowflake specifiers unsupported in DuckDB to strftime format codes
2002
- EXTRACT_STRFTIME_MAPPINGS: dict[str, tuple[str, str]] = {
2004
+ EXTRACT_STRFTIME_MAPPINGS: t.ClassVar[dict[str, tuple[str, str]]] = {
2003
2005
  "WEEKISO": ("%V", "INTEGER"),
2004
2006
  "YEAROFWEEK": ("%G", "INTEGER"),
2005
2007
  "YEAROFWEEKISO": ("%G", "INTEGER"),
@@ -2007,7 +2009,7 @@ class DuckDBGenerator(generator.Generator):
2007
2009
  }
2008
2010
 
2009
2011
  # Maps epoch-based specifiers to DuckDB epoch functions
2010
- EXTRACT_EPOCH_MAPPINGS: dict[str, str] = {
2012
+ EXTRACT_EPOCH_MAPPINGS: t.ClassVar[dict[str, str]] = {
2011
2013
  "EPOCH_SECOND": "EPOCH",
2012
2014
  "EPOCH_MILLISECOND": "EPOCH_MS",
2013
2015
  "EPOCH_MICROSECOND": "EPOCH_US",
@@ -2057,7 +2059,7 @@ class DuckDBGenerator(generator.Generator):
2057
2059
  # - Large format: Fixed 10-byte header + values (no padding needed)
2058
2060
  # Result: Complete binary bitmap as BLOB
2059
2061
  #
2060
- BITMAP_CONSTRUCT_AGG_TEMPLATE: exp.Expr = exp.maybe_parse(
2062
+ BITMAP_CONSTRUCT_AGG_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2061
2063
  """
2062
2064
  SELECT CASE
2063
2065
  WHEN l IS NULL OR LENGTH(l) = 0 THEN NULL
@@ -2076,7 +2078,7 @@ class DuckDBGenerator(generator.Generator):
2076
2078
  )
2077
2079
 
2078
2080
  # Template for RANDSTR transpilation - placeholders get replaced with actual parameters
2079
- RANDSTR_TEMPLATE: exp.Expr = exp.maybe_parse(
2081
+ RANDSTR_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2080
2082
  f"""
2081
2083
  SELECT LISTAGG(
2082
2084
  SUBSTRING(
@@ -2096,7 +2098,7 @@ class DuckDBGenerator(generator.Generator):
2096
2098
  # Template for MINHASH transpilation
2097
2099
  # Computes k minimum hash values across aggregated data using DuckDB list functions
2098
2100
  # Returns JSON matching Snowflake format: {"state": [...], "type": "minhash", "version": 1}
2099
- MINHASH_TEMPLATE: exp.Expr = exp.maybe_parse(
2101
+ MINHASH_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2100
2102
  """
2101
2103
  SELECT JSON_OBJECT('state', LIST(min_h ORDER BY seed), 'type', 'minhash', 'version', 1)
2102
2104
  FROM (
@@ -2108,7 +2110,7 @@ class DuckDBGenerator(generator.Generator):
2108
2110
 
2109
2111
  # Template for MINHASH_COMBINE transpilation
2110
2112
  # Combines multiple minhash signatures by taking element-wise minimum
2111
- MINHASH_COMBINE_TEMPLATE: exp.Expr = exp.maybe_parse(
2113
+ MINHASH_COMBINE_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2112
2114
  """
2113
2115
  SELECT JSON_OBJECT('state', LIST(min_h ORDER BY idx), 'type', 'minhash', 'version', 1)
2114
2116
  FROM (
@@ -2125,7 +2127,7 @@ class DuckDBGenerator(generator.Generator):
2125
2127
 
2126
2128
  # Template for APPROXIMATE_SIMILARITY transpilation
2127
2129
  # Computes multi-way Jaccard similarity: fraction of positions where ALL signatures agree
2128
- APPROXIMATE_SIMILARITY_TEMPLATE: exp.Expr = exp.maybe_parse(
2130
+ APPROXIMATE_SIMILARITY_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2129
2131
  """
2130
2132
  SELECT CAST(SUM(CASE WHEN num_distinct = 1 THEN 1 ELSE 0 END) AS DOUBLE) / COUNT(*)
2131
2133
  FROM (
@@ -2143,7 +2145,7 @@ class DuckDBGenerator(generator.Generator):
2143
2145
  # Template for ARRAYS_ZIP transpilation
2144
2146
  # Snowflake pads to longest array; DuckDB LIST_ZIP truncates to shortest
2145
2147
  # Uses RANGE + indexing to match Snowflake behavior
2146
- ARRAYS_ZIP_TEMPLATE: exp.Expr = exp.maybe_parse(
2148
+ ARRAYS_ZIP_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2147
2149
  """
2148
2150
  CASE WHEN :null_check THEN NULL
2149
2151
  WHEN :all_empty_check THEN [:empty_struct]
@@ -2152,7 +2154,7 @@ class DuckDBGenerator(generator.Generator):
2152
2154
  """,
2153
2155
  )
2154
2156
 
2155
- UUID_V5_TEMPLATE: exp.Expr = exp.maybe_parse(
2157
+ UUID_V5_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2156
2158
  """
2157
2159
  (SELECT
2158
2160
  LOWER(
@@ -2176,7 +2178,7 @@ class DuckDBGenerator(generator.Generator):
2176
2178
  # INTERSECTION (<=): keep the N-th occurrence only if N <= count in arr2
2177
2179
  # e.g. [2,2,2] INTERSECT [2,2] -> [2,2]
2178
2180
  # IS NOT DISTINCT FROM is used for NULL-safe element comparison.
2179
- ARRAY_BAG_TEMPLATE: exp.Expr = exp.maybe_parse(
2181
+ ARRAY_BAG_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2180
2182
  """
2181
2183
  CASE
2182
2184
  WHEN :arr1 IS NULL OR :arr2 IS NULL THEN NULL
@@ -2191,12 +2193,12 @@ class DuckDBGenerator(generator.Generator):
2191
2193
  """
2192
2194
  )
2193
2195
 
2194
- ARRAY_EXCEPT_CONDITION: exp.Expr = exp.maybe_parse(
2196
+ ARRAY_EXCEPT_CONDITION: t.ClassVar[exp.Expr] = exp.maybe_parse(
2195
2197
  "LEN(LIST_FILTER(:arr1[1:pair[1]], e -> e IS NOT DISTINCT FROM pair[0]))"
2196
2198
  " > LEN(LIST_FILTER(:arr2, e -> e IS NOT DISTINCT FROM pair[0]))"
2197
2199
  )
2198
2200
 
2199
- ARRAY_INTERSECTION_CONDITION: exp.Expr = exp.maybe_parse(
2201
+ ARRAY_INTERSECTION_CONDITION: t.ClassVar[exp.Expr] = exp.maybe_parse(
2200
2202
  "LEN(LIST_FILTER(:arr1[1:pair[1]], e -> e IS NOT DISTINCT FROM pair[0]))"
2201
2203
  " <= LEN(LIST_FILTER(:arr2, e -> e IS NOT DISTINCT FROM pair[0]))"
2202
2204
  )
@@ -2205,7 +2207,7 @@ class DuckDBGenerator(generator.Generator):
2205
2207
  # filters out any element that appears at least once in arr2.
2206
2208
  # e.g. [1,1,2,3] EXCEPT [1] -> [2,3]
2207
2209
  # IS NOT DISTINCT FROM is used for NULL-safe element comparison.
2208
- ARRAY_EXCEPT_SET_TEMPLATE: exp.Expr = exp.maybe_parse(
2210
+ ARRAY_EXCEPT_SET_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2209
2211
  """
2210
2212
  CASE
2211
2213
  WHEN :arr1 IS NULL OR :arr2 IS NULL THEN NULL
@@ -2225,7 +2227,7 @@ class DuckDBGenerator(generator.Generator):
2225
2227
  # 1 IN UNNEST([]) -> FALSE
2226
2228
  # The default `IN (SELECT UNNEST(...))` rewrite creates a correlated subquery
2227
2229
  # that DuckDB rejects inside non-inner joins, so a CASE expression is used instead.
2228
- IN_UNNEST_TEMPLATE: exp.Expr = exp.maybe_parse(
2230
+ IN_UNNEST_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2229
2231
  """
2230
2232
  CASE
2231
2233
  WHEN :arr IS NULL OR ARRAY_LENGTH(:arr) = 0 THEN FALSE
@@ -2236,7 +2238,7 @@ class DuckDBGenerator(generator.Generator):
2236
2238
  """
2237
2239
  )
2238
2240
 
2239
- STRTOK_TO_ARRAY_TEMPLATE: exp.Expr = exp.maybe_parse(
2241
+ STRTOK_TO_ARRAY_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2240
2242
  """
2241
2243
  CASE WHEN :delimiter IS NULL THEN NULL
2242
2244
  ELSE LIST_FILTER(
@@ -2285,7 +2287,7 @@ class DuckDBGenerator(generator.Generator):
2285
2287
  # x -> NOT x = ''
2286
2288
  # )[index]
2287
2289
  # END
2288
- STRTOK_TEMPLATE: exp.Expr = exp.maybe_parse(
2290
+ STRTOK_TEMPLATE: t.ClassVar[exp.Expr] = exp.maybe_parse(
2289
2291
  """
2290
2292
  CASE
2291
2293
  WHEN :delimiter = '' AND :string = '' THEN NULL