sqlglot 26.30.0__py3-none-any.whl → 26.31.0__py3-none-any.whl

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.
sqlglot/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '26.30.0'
21
- __version_tuple__ = version_tuple = (26, 30, 0)
20
+ __version__ = version = '26.31.0'
21
+ __version_tuple__ = version_tuple = (26, 31, 0)
@@ -93,6 +93,7 @@ DIALECTS = [
93
93
  "Teradata",
94
94
  "Trino",
95
95
  "TSQL",
96
+ "Exasol",
96
97
  ]
97
98
 
98
99
  MODULE_BY_DIALECT = {name: name.lower() for name in DIALECTS}
@@ -543,7 +543,7 @@ class BigQuery(Dialect):
543
543
  "DATE_ADD": build_date_delta_with_interval(exp.DateAdd),
544
544
  "DATE_SUB": build_date_delta_with_interval(exp.DateSub),
545
545
  "DATE_TRUNC": lambda args: exp.DateTrunc(
546
- unit=exp.Literal.string(str(seq_get(args, 1))),
546
+ unit=seq_get(args, 1),
547
547
  this=seq_get(args, 0),
548
548
  zone=seq_get(args, 2),
549
549
  ),
@@ -963,9 +963,6 @@ class BigQuery(Dialect):
963
963
  exp.DateSub: date_add_interval_sql("DATE", "SUB"),
964
964
  exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
965
965
  exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
966
- exp.DateTrunc: lambda self, e: self.func(
967
- "DATE_TRUNC", e.this, e.text("unit"), e.args.get("zone")
968
- ),
969
966
  exp.FromTimeZone: lambda self, e: self.func(
970
967
  "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
971
968
  ),
@@ -1195,6 +1192,11 @@ class BigQuery(Dialect):
1195
1192
  "within",
1196
1193
  }
1197
1194
 
1195
+ def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
1196
+ unit = expression.unit
1197
+ unit_sql = unit.name if unit.is_string else self.sql(unit)
1198
+ return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
1199
+
1198
1200
  def mod_sql(self, expression: exp.Mod) -> str:
1199
1201
  this = expression.this
1200
1202
  expr = expression.expression
@@ -9,6 +9,7 @@ from sqlglot.dialects.dialect import (
9
9
  build_date_delta,
10
10
  timestamptrunc_sql,
11
11
  build_formatted_time,
12
+ groupconcat_sql,
12
13
  )
13
14
  from sqlglot.dialects.spark import Spark
14
15
  from sqlglot.tokens import TokenType
@@ -87,6 +88,7 @@ class Databricks(Spark):
87
88
  e.this,
88
89
  ),
89
90
  exp.DatetimeTrunc: timestamptrunc_sql(),
91
+ exp.GroupConcat: groupconcat_sql,
90
92
  exp.Select: transforms.preprocess(
91
93
  [
92
94
  transforms.eliminate_distinct_on,
@@ -96,6 +96,7 @@ class Dialects(str, Enum):
96
96
  TERADATA = "teradata"
97
97
  TRINO = "trino"
98
98
  TSQL = "tsql"
99
+ EXASOL = "exasol"
99
100
 
100
101
 
101
102
  class NormalizationStrategy(str, AutoName):
@@ -700,6 +701,9 @@ class Dialect(metaclass=_Dialect):
700
701
  exp.TimeAdd,
701
702
  exp.TimeSub,
702
703
  },
704
+ exp.DataType.Type.TIMESTAMPTZ: {
705
+ exp.CurrentTimestampLTZ,
706
+ },
703
707
  exp.DataType.Type.TIMESTAMP: {
704
708
  exp.CurrentTimestamp,
705
709
  exp.StrToTime,
@@ -1906,21 +1910,23 @@ def groupconcat_sql(
1906
1910
 
1907
1911
 
1908
1912
  def build_timetostr_or_tochar(args: t.List, dialect: Dialect) -> exp.TimeToStr | exp.ToChar:
1909
- this = seq_get(args, 0)
1910
- format = seq_get(args, 1)
1911
-
1912
- if this:
1913
+ if len(args) == 2:
1914
+ this = args[0]
1913
1915
  if not this.type:
1914
1916
  from sqlglot.optimizer.annotate_types import annotate_types
1915
1917
 
1916
1918
  annotate_types(this, dialect=dialect)
1917
1919
 
1918
- from sqlglot.dialects import Snowflake
1919
-
1920
- if this.is_type(*exp.DataType.TEMPORAL_TYPES) or (
1921
- isinstance(format, exp.Literal) and format.name in Snowflake.TIME_MAPPING
1922
- ):
1920
+ if this.is_type(*exp.DataType.TEMPORAL_TYPES):
1923
1921
  dialect_name = dialect.__class__.__name__.lower()
1924
1922
  return build_formatted_time(exp.TimeToStr, dialect_name, default=True)(args)
1925
1923
 
1926
1924
  return exp.ToChar.from_arg_list(args)
1925
+
1926
+
1927
+ def build_replace_with_optional_replacement(args: t.List) -> exp.Replace:
1928
+ return exp.Replace(
1929
+ this=seq_get(args, 0),
1930
+ expression=seq_get(args, 1),
1931
+ replacement=seq_get(args, 2) or exp.Literal.string(""),
1932
+ )
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+ from sqlglot import exp, generator
3
+ from sqlglot.dialects.dialect import Dialect, rename_func
4
+
5
+
6
+ class Exasol(Dialect):
7
+ class Generator(generator.Generator):
8
+ # https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm#StringDataType
9
+ STRING_TYPE_MAPPING = {
10
+ exp.DataType.Type.BLOB: "VARCHAR",
11
+ exp.DataType.Type.LONGBLOB: "VARCHAR",
12
+ exp.DataType.Type.LONGTEXT: "VARCHAR",
13
+ exp.DataType.Type.MEDIUMBLOB: "VARCHAR",
14
+ exp.DataType.Type.MEDIUMTEXT: "VARCHAR",
15
+ exp.DataType.Type.TINYBLOB: "VARCHAR",
16
+ exp.DataType.Type.TINYTEXT: "VARCHAR",
17
+ exp.DataType.Type.TEXT: "VARCHAR",
18
+ exp.DataType.Type.VARBINARY: "VARCHAR",
19
+ }
20
+
21
+ # https://docs.exasol.com/db/latest/sql_references/data_types/datatypealiases.htm
22
+ TYPE_MAPPING = {
23
+ **generator.Generator.TYPE_MAPPING,
24
+ **STRING_TYPE_MAPPING,
25
+ exp.DataType.Type.TINYINT: "SMALLINT",
26
+ exp.DataType.Type.MEDIUMINT: "INT",
27
+ exp.DataType.Type.DECIMAL32: "DECIMAL",
28
+ exp.DataType.Type.DECIMAL64: "DECIMAL",
29
+ exp.DataType.Type.DECIMAL128: "DECIMAL",
30
+ exp.DataType.Type.DECIMAL256: "DECIMAL",
31
+ exp.DataType.Type.DATETIME: "TIMESTAMP",
32
+ }
33
+
34
+ def datatype_sql(self, expression: exp.DataType) -> str:
35
+ # Exasol supports a fixed default precision of 3 for TIMESTAMP WITH LOCAL TIME ZONE
36
+ # and does not allow specifying a different custom precision
37
+ if expression.is_type(exp.DataType.Type.TIMESTAMPLTZ):
38
+ return "TIMESTAMP WITH LOCAL TIME ZONE"
39
+
40
+ return super().datatype_sql(expression)
41
+
42
+ TRANSFORMS = {
43
+ **generator.Generator.TRANSFORMS,
44
+ # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/mod.htm
45
+ exp.Mod: rename_func("MOD"),
46
+ }
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from sqlglot import exp
4
4
  from sqlglot.dialects.dialect import NormalizationStrategy
5
5
  from sqlglot.dialects.tsql import TSQL
6
+ from sqlglot.tokens import TokenType
6
7
 
7
8
 
8
9
  class Fabric(TSQL):
@@ -28,61 +29,87 @@ class Fabric(TSQL):
28
29
  # Fabric is case-sensitive unlike T-SQL which is case-insensitive
29
30
  NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE
30
31
 
32
+ class Tokenizer(TSQL.Tokenizer):
33
+ # Override T-SQL tokenizer to handle TIMESTAMP differently
34
+ # In T-SQL, TIMESTAMP is a synonym for ROWVERSION, but in Fabric we want it to be a datetime type
35
+ # Also add UTINYINT keyword mapping since T-SQL doesn't have it
36
+ KEYWORDS = {
37
+ **TSQL.Tokenizer.KEYWORDS,
38
+ "TIMESTAMP": TokenType.TIMESTAMP,
39
+ "UTINYINT": TokenType.UTINYINT,
40
+ }
41
+
31
42
  class Generator(TSQL.Generator):
32
43
  # Fabric-specific type mappings - override T-SQL types that aren't supported
33
44
  # Reference: https://learn.microsoft.com/en-us/fabric/data-warehouse/data-types
34
45
  TYPE_MAPPING = {
35
46
  **TSQL.Generator.TYPE_MAPPING,
36
- # Fabric doesn't support these types, map to alternatives
47
+ exp.DataType.Type.DATETIME: "DATETIME2",
48
+ exp.DataType.Type.DECIMAL: "DECIMAL",
49
+ exp.DataType.Type.IMAGE: "VARBINARY",
50
+ exp.DataType.Type.INT: "INT",
51
+ exp.DataType.Type.JSON: "VARCHAR",
37
52
  exp.DataType.Type.MONEY: "DECIMAL",
38
- exp.DataType.Type.SMALLMONEY: "DECIMAL",
39
- exp.DataType.Type.DATETIME: "DATETIME2(6)",
40
- exp.DataType.Type.SMALLDATETIME: "DATETIME2(6)",
41
53
  exp.DataType.Type.NCHAR: "CHAR",
42
54
  exp.DataType.Type.NVARCHAR: "VARCHAR",
43
- exp.DataType.Type.TEXT: "VARCHAR(MAX)",
44
- exp.DataType.Type.IMAGE: "VARBINARY",
55
+ exp.DataType.Type.ROWVERSION: "ROWVERSION",
56
+ exp.DataType.Type.SMALLDATETIME: "DATETIME2",
57
+ exp.DataType.Type.SMALLMONEY: "DECIMAL",
58
+ exp.DataType.Type.TIMESTAMP: "DATETIME2",
59
+ exp.DataType.Type.TIMESTAMPNTZ: "DATETIME2",
60
+ exp.DataType.Type.TIMESTAMPTZ: "DATETIMEOFFSET",
45
61
  exp.DataType.Type.TINYINT: "SMALLINT",
46
- exp.DataType.Type.UTINYINT: "SMALLINT", # T-SQL parses TINYINT as UTINYINT
47
- exp.DataType.Type.JSON: "VARCHAR",
62
+ exp.DataType.Type.UTINYINT: "SMALLINT",
63
+ exp.DataType.Type.UUID: "VARBINARY(MAX)",
48
64
  exp.DataType.Type.XML: "VARCHAR",
49
- exp.DataType.Type.UUID: "VARBINARY(MAX)", # UNIQUEIDENTIFIER has limitations in Fabric
50
- # Override T-SQL mappings that use different names in Fabric
51
- exp.DataType.Type.DECIMAL: "DECIMAL", # T-SQL uses NUMERIC
52
- exp.DataType.Type.DOUBLE: "FLOAT",
53
- exp.DataType.Type.INT: "INT", # T-SQL uses INTEGER
54
65
  }
55
66
 
56
67
  def datatype_sql(self, expression: exp.DataType) -> str:
57
- """
58
- Override datatype generation to handle Fabric-specific precision limitations.
59
-
60
- Fabric limits temporal types (TIME, DATETIME2, DATETIMEOFFSET) to max 6 digits precision.
61
- When no precision is specified, we default to 6 digits.
62
- """
63
- if expression.is_type(
64
- exp.DataType.Type.TIME,
65
- exp.DataType.Type.DATETIME2,
66
- exp.DataType.Type.TIMESTAMPTZ, # DATETIMEOFFSET in Fabric
68
+ # Check if this is a temporal type that needs precision handling. Fabric limits temporal
69
+ # types to max 6 digits precision. When no precision is specified, we default to 6 digits.
70
+ if (
71
+ expression.is_type(*exp.DataType.TEMPORAL_TYPES)
72
+ and expression.this != exp.DataType.Type.DATE
67
73
  ):
68
74
  # Get the current precision (first expression if it exists)
69
- precision = expression.find(exp.DataTypeParam)
75
+ precision_param = expression.find(exp.DataTypeParam)
76
+ target_precision = 6
70
77
 
71
- # Determine the target precision
72
- if precision is None:
73
- # No precision specified, default to 6
74
- target_precision = 6
75
- elif precision.this.is_int:
78
+ if precision_param and precision_param.this.is_int:
76
79
  # Cap precision at 6
77
- current_precision = precision.this.to_py()
80
+ current_precision = precision_param.this.to_py()
78
81
  target_precision = min(current_precision, 6)
82
+ else:
83
+ # If precision exists but is not an integer, default to 6
84
+ target_precision = 6
79
85
 
80
86
  # Create a new expression with the target precision
81
- new_expression = exp.DataType(
87
+ expression = exp.DataType(
82
88
  this=expression.this,
83
89
  expressions=[exp.DataTypeParam(this=exp.Literal.number(target_precision))],
84
90
  )
85
91
 
86
- return super().datatype_sql(new_expression)
87
-
88
92
  return super().datatype_sql(expression)
93
+
94
+ def unixtotime_sql(self, expression: exp.UnixToTime) -> str:
95
+ scale = expression.args.get("scale")
96
+ timestamp = expression.this
97
+
98
+ if scale not in (None, exp.UnixToTime.SECONDS):
99
+ self.unsupported(f"UnixToTime scale {scale} is not supported by Fabric")
100
+ return ""
101
+
102
+ # Convert unix timestamp (seconds) to microseconds and round to avoid decimals
103
+ microseconds = timestamp * exp.Literal.number("1e6")
104
+ rounded = exp.func("round", microseconds, 0)
105
+ rounded_ms_as_bigint = exp.cast(rounded, exp.DataType.Type.BIGINT)
106
+
107
+ # Create the base datetime as '1970-01-01' cast to DATETIME2(6)
108
+ epoch_start = exp.cast("'1970-01-01'", "datetime2(6)", dialect="fabric")
109
+
110
+ dateadd = exp.DateAdd(
111
+ this=epoch_start,
112
+ expression=rounded_ms_as_bigint,
113
+ unit=exp.Literal.string("MICROSECONDS"),
114
+ )
115
+ return self.sql(dateadd)
@@ -8,6 +8,7 @@ from sqlglot.dialects.dialect import (
8
8
  NormalizationStrategy,
9
9
  binary_from_function,
10
10
  bool_xor_sql,
11
+ build_replace_with_optional_replacement,
11
12
  date_trunc_to_time,
12
13
  datestrtodate_sql,
13
14
  encode_decode_sql,
@@ -360,6 +361,7 @@ class Presto(Dialect):
360
361
  expression=seq_get(args, 1),
361
362
  replacement=seq_get(args, 2) or exp.Literal.string(""),
362
363
  ),
364
+ "REPLACE": build_replace_with_optional_replacement,
363
365
  "ROW": exp.Struct.from_arg_list,
364
366
  "SEQUENCE": exp.GenerateSeries.from_arg_list,
365
367
  "SET_AGG": exp.ArrayUniqueAgg.from_arg_list,
@@ -213,8 +213,7 @@ class Redshift(Postgres):
213
213
  exp.TableSample: no_tablesample_sql,
214
214
  exp.TsOrDsAdd: date_delta_sql("DATEADD"),
215
215
  exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
216
- exp.UnixToTime: lambda self,
217
- e: f"(TIMESTAMP 'epoch' + {self.sql(e.this)} * INTERVAL '1 SECOND')",
216
+ exp.UnixToTime: lambda self, e: self._unix_to_time_sql(e),
218
217
  }
219
218
 
220
219
  # Postgres maps exp.Pivot to no_pivot_sql, but Redshift support pivots
@@ -447,3 +446,12 @@ class Redshift(Postgres):
447
446
  def explode_sql(self, expression: exp.Explode) -> str:
448
447
  self.unsupported("Unsupported EXPLODE() function")
449
448
  return ""
449
+
450
+ def _unix_to_time_sql(self, expression: exp.UnixToTime) -> str:
451
+ scale = expression.args.get("scale")
452
+ this = self.sql(expression.this)
453
+
454
+ if scale is not None and scale != exp.UnixToTime.SECONDS and scale.is_int:
455
+ this = f"({this} / POWER(10, {scale.to_py()}))"
456
+
457
+ return f"(TIMESTAMP 'epoch' + {this} * INTERVAL '1 SECOND')"
@@ -9,6 +9,7 @@ from sqlglot.dialects.dialect import (
9
9
  build_timetostr_or_tochar,
10
10
  binary_from_function,
11
11
  build_default_decimal_type,
12
+ build_replace_with_optional_replacement,
12
13
  build_timestamp_from_parts,
13
14
  date_delta_sql,
14
15
  date_trunc_to_time,
@@ -484,6 +485,7 @@ class Snowflake(Dialect):
484
485
  "REGEXP_REPLACE": _build_regexp_replace,
485
486
  "REGEXP_SUBSTR": _build_regexp_extract(exp.RegexpExtract),
486
487
  "REGEXP_SUBSTR_ALL": _build_regexp_extract(exp.RegexpExtractAll),
488
+ "REPLACE": build_replace_with_optional_replacement,
487
489
  "RLIKE": exp.RegexpLike.from_arg_list,
488
490
  "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)),
489
491
  "TABLE": lambda args: exp.TableFromRows(this=seq_get(args, 0)),
@@ -1416,7 +1418,7 @@ class Snowflake(Dialect):
1416
1418
 
1417
1419
  def timetostr_sql(self, expression: exp.TimeToStr) -> str:
1418
1420
  this = expression.this
1419
- if not isinstance(this, exp.TsOrDsToTimestamp):
1421
+ if this.is_string:
1420
1422
  this = exp.cast(this, exp.DataType.Type.TIMESTAMP)
1421
1423
 
1422
1424
  return self.func("TO_CHAR", this, self.format_time(expression))
sqlglot/dialects/tsql.py CHANGED
@@ -612,6 +612,7 @@ class TSQL(Dialect):
612
612
  "SYSDATETIME": exp.CurrentTimestamp.from_arg_list,
613
613
  "SUSER_NAME": exp.CurrentUser.from_arg_list,
614
614
  "SUSER_SNAME": exp.CurrentUser.from_arg_list,
615
+ "SYSDATETIMEOFFSET": exp.CurrentTimestampLTZ.from_arg_list,
615
616
  "SYSTEM_USER": exp.CurrentUser.from_arg_list,
616
617
  "TIMEFROMPARTS": _build_timefromparts,
617
618
  "DATETRUNC": _build_datetrunc,
@@ -1020,6 +1021,7 @@ class TSQL(Dialect):
1020
1021
  exp.CTE: transforms.preprocess([qualify_derived_table_outputs]),
1021
1022
  exp.CurrentDate: rename_func("GETDATE"),
1022
1023
  exp.CurrentTimestamp: rename_func("GETDATE"),
1024
+ exp.CurrentTimestampLTZ: rename_func("SYSDATETIMEOFFSET"),
1023
1025
  exp.DateStrToDate: datestrtodate_sql,
1024
1026
  exp.Extract: rename_func("DATEPART"),
1025
1027
  exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,
@@ -1249,15 +1251,15 @@ class TSQL(Dialect):
1249
1251
  sql_with_ctes = self.prepend_ctes(expression, sql)
1250
1252
  sql_literal = self.sql(exp.Literal.string(sql_with_ctes))
1251
1253
  if kind == "SCHEMA":
1252
- return f"""IF NOT EXISTS (SELECT * FROM information_schema.schemata WHERE schema_name = {identifier}) EXEC({sql_literal})"""
1254
+ return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = {identifier}) EXEC({sql_literal})"""
1253
1255
  elif kind == "TABLE":
1254
1256
  assert table
1255
1257
  where = exp.and_(
1256
- exp.column("table_name").eq(table.name),
1257
- exp.column("table_schema").eq(table.db) if table.db else None,
1258
- exp.column("table_catalog").eq(table.catalog) if table.catalog else None,
1258
+ exp.column("TABLE_NAME").eq(table.name),
1259
+ exp.column("TABLE_SCHEMA").eq(table.db) if table.db else None,
1260
+ exp.column("TABLE_CATALOG").eq(table.catalog) if table.catalog else None,
1259
1261
  )
1260
- return f"""IF NOT EXISTS (SELECT * FROM information_schema.tables WHERE {where}) EXEC({sql_literal})"""
1262
+ return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE {where}) EXEC({sql_literal})"""
1261
1263
  elif kind == "INDEX":
1262
1264
  index = self.sql(exp.Literal.string(expression.this.text("this")))
1263
1265
  return f"""IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id({identifier}) AND name = {index}) EXEC({sql_literal})"""
sqlglot/expressions.py CHANGED
@@ -5806,6 +5806,10 @@ class CurrentTimestamp(Func):
5806
5806
  arg_types = {"this": False, "sysdate": False}
5807
5807
 
5808
5808
 
5809
+ class CurrentTimestampLTZ(Func):
5810
+ arg_types = {}
5811
+
5812
+
5809
5813
  class CurrentSchema(Func):
5810
5814
  arg_types = {"this": False}
5811
5815
 
@@ -5846,8 +5850,6 @@ class DateTrunc(Func):
5846
5850
  unit_name = TimeUnit.UNABBREVIATED_UNIT_NAME[unit_name]
5847
5851
 
5848
5852
  args["unit"] = Literal.string(unit_name)
5849
- elif isinstance(unit, Week):
5850
- unit.set("this", Literal.string(unit.this.name.upper()))
5851
5853
 
5852
5854
  super().__init__(**args)
5853
5855
 
@@ -6669,6 +6671,11 @@ class Repeat(Func):
6669
6671
  arg_types = {"this": True, "times": True}
6670
6672
 
6671
6673
 
6674
+ # Some dialects like Snowflake support two argument replace
6675
+ class Replace(Func):
6676
+ arg_types = {"this": True, "expression": True, "replacement": False}
6677
+
6678
+
6672
6679
  # https://learn.microsoft.com/en-us/sql/t-sql/functions/round-transact-sql?view=sql-server-ver16
6673
6680
  # tsql third argument function == trunctaion if not 0
6674
6681
  class Round(Func):
sqlglot/generator.py CHANGED
@@ -3480,7 +3480,7 @@ class Generator(metaclass=_Generator):
3480
3480
 
3481
3481
  actions_list.append(action_sql)
3482
3482
 
3483
- actions_sql = self.format_args(*actions_list)
3483
+ actions_sql = self.format_args(*actions_list).lstrip("\n")
3484
3484
 
3485
3485
  exists = " IF EXISTS" if expression.args.get("exists") else ""
3486
3486
  on_cluster = self.sql(expression, "cluster")
@@ -3491,7 +3491,7 @@ class Generator(metaclass=_Generator):
3491
3491
  kind = self.sql(expression, "kind")
3492
3492
  not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3493
3493
 
3494
- return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions_sql}{not_valid}{options}"
3494
+ return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3495
3495
 
3496
3496
  def add_column_sql(self, expression: exp.Expression) -> str:
3497
3497
  sql = self.sql(expression)
@@ -3510,7 +3510,7 @@ class Generator(metaclass=_Generator):
3510
3510
  return f"DROP{exists}{expressions}"
3511
3511
 
3512
3512
  def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3513
- return f"ADD {self.expressions(expression)}"
3513
+ return f"ADD {self.expressions(expression, indent=False)}"
3514
3514
 
3515
3515
  def addpartition_sql(self, expression: exp.AddPartition) -> str:
3516
3516
  exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
@@ -358,7 +358,7 @@ class Scope:
358
358
  for expression in itertools.chain(self.derived_tables, self.udtfs):
359
359
  self._references.append(
360
360
  (
361
- expression.alias,
361
+ _get_source_alias(expression),
362
362
  expression if expression.args.get("pivots") else expression.unnest(),
363
363
  )
364
364
  )
@@ -785,7 +785,7 @@ def _traverse_tables(scope):
785
785
  # This shouldn't be a problem once qualify_columns runs, as it adds aliases on everything.
786
786
  # Until then, this means that only a single, unaliased derived table is allowed (rather,
787
787
  # the latest one wins.
788
- sources[expression.alias] = child_scope
788
+ sources[_get_source_alias(expression)] = child_scope
789
789
 
790
790
  # append the final child_scope yielded
791
791
  if child_scope:
@@ -825,7 +825,7 @@ def _traverse_udtfs(scope):
825
825
  ):
826
826
  yield child_scope
827
827
  top = child_scope
828
- sources[expression.alias] = child_scope
828
+ sources[_get_source_alias(expression)] = child_scope
829
829
 
830
830
  scope.subquery_scopes.append(top)
831
831
 
@@ -915,3 +915,13 @@ def find_in_scope(expression, expression_types, bfs=True):
915
915
  the criteria was found.
916
916
  """
917
917
  return next(find_all_in_scope(expression, expression_types, bfs=bfs), None)
918
+
919
+
920
+ def _get_source_alias(expression):
921
+ alias_arg = expression.args.get("alias")
922
+ alias_name = expression.alias
923
+
924
+ if not alias_name and isinstance(alias_arg, exp.TableAlias) and len(alias_arg.columns) == 1:
925
+ alias_name = alias_arg.columns[0].name
926
+
927
+ return alias_name
sqlglot/parser.py CHANGED
@@ -7362,8 +7362,9 @@ class Parser(metaclass=_Parser):
7362
7362
 
7363
7363
  return None
7364
7364
 
7365
- if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN or self._match_text_seq(
7366
- "COLUMNS"
7365
+ if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
7366
+ not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
7367
+ or self._match_text_seq("COLUMNS")
7367
7368
  ):
7368
7369
  schema = self._parse_schema()
7369
7370
 
sqlglot/transforms.py CHANGED
@@ -352,13 +352,20 @@ def unnest_to_explode(
352
352
  has_multi_expr = len(exprs) > 1
353
353
  this, *expressions = _unnest_zip_exprs(unnest, exprs, has_multi_expr)
354
354
 
355
+ columns = alias.columns if alias else []
356
+ offset = unnest.args.get("offset")
357
+ if offset:
358
+ columns.insert(
359
+ 0, offset if isinstance(offset, exp.Identifier) else exp.to_identifier("pos")
360
+ )
361
+
355
362
  unnest.replace(
356
363
  exp.Table(
357
364
  this=_udtf_type(unnest, has_multi_expr)(
358
365
  this=this,
359
366
  expressions=expressions,
360
367
  ),
361
- alias=exp.TableAlias(this=alias.this, columns=alias.columns) if alias else None,
368
+ alias=exp.TableAlias(this=alias.this, columns=columns) if alias else None,
362
369
  )
363
370
  )
364
371
 
@@ -393,6 +400,13 @@ def unnest_to_explode(
393
400
  "CROSS JOIN UNNEST to LATERAL VIEW EXPLODE transformation requires explicit column aliases"
394
401
  )
395
402
 
403
+ offset = unnest.args.get("offset")
404
+ if offset:
405
+ alias_cols.insert(
406
+ 0,
407
+ offset if isinstance(offset, exp.Identifier) else exp.to_identifier("pos"),
408
+ )
409
+
396
410
  for e, column in zip(exprs, alias_cols):
397
411
  expression.append(
398
412
  "laterals",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlglot
3
- Version: 26.30.0
3
+ Version: 26.31.0
4
4
  Summary: An easily customizable SQL parser and transpiler
5
5
  Author-email: Toby Mao <toby.mao@gmail.com>
6
6
  License: MIT License
@@ -61,7 +61,7 @@ Dynamic: provides-extra
61
61
 
62
62
  ![SQLGlot logo](sqlglot.png)
63
63
 
64
- SQLGlot is a no-dependency SQL parser, transpiler, optimizer, and engine. It can be used to format SQL or translate between [27 different dialects](https://github.com/tobymao/sqlglot/blob/main/sqlglot/dialects/__init__.py) like [DuckDB](https://duckdb.org/), [Presto](https://prestodb.io/) / [Trino](https://trino.io/), [Spark](https://spark.apache.org/) / [Databricks](https://www.databricks.com/), [Snowflake](https://www.snowflake.com/en/), and [BigQuery](https://cloud.google.com/bigquery/). It aims to read a wide variety of SQL inputs and output syntactically and semantically correct SQL in the targeted dialects.
64
+ SQLGlot is a no-dependency SQL parser, transpiler, optimizer, and engine. It can be used to format SQL or translate between [29 different dialects](https://github.com/tobymao/sqlglot/blob/main/sqlglot/dialects/__init__.py) like [DuckDB](https://duckdb.org/), [Presto](https://prestodb.io/) / [Trino](https://trino.io/), [Spark](https://spark.apache.org/) / [Databricks](https://www.databricks.com/), [Snowflake](https://www.snowflake.com/en/), and [BigQuery](https://cloud.google.com/bigquery/). It aims to read a wide variety of SQL inputs and output syntactically and semantically correct SQL in the targeted dialects.
65
65
 
66
66
  It is a very comprehensive generic SQL parser with a robust [test suite](https://github.com/tobymao/sqlglot/blob/main/tests/). It is also quite [performant](#benchmarks), while being written purely in Python.
67
67
 
@@ -1,45 +1,46 @@
1
1
  sqlglot/__init__.py,sha256=za08rtdPh2v7dOpGdNomttlIVGgTrKja7rPd6sQwaTg,5391
2
2
  sqlglot/__main__.py,sha256=022c173KqxsiABWTEpUIq_tJUxuNiW7a7ABsxBXqvu8,2069
3
3
  sqlglot/_typing.py,sha256=-1HPyr3w5COlSJWqlgt8jhFk2dyMvBuvVBqIX1wyVCM,642
4
- sqlglot/_version.py,sha256=FTiVUaTaSWoKVYxiizmUH00aN8nwszEzKZ3Dh2gUm7s,515
4
+ sqlglot/_version.py,sha256=X5X34o5ymsD4ydxIloUOjJGcZ-0Zi6rgP-736DtnUZ8,515
5
5
  sqlglot/diff.py,sha256=PtOllQMQa1Sw1-V2Y8eypmDqGujXYPaTOp_WLsWkAWk,17314
6
6
  sqlglot/errors.py,sha256=QNKMr-pzLUDR-tuMmn_GK6iMHUIVdb_YSJ_BhGEvuso,2126
7
- sqlglot/expressions.py,sha256=QTf40Yu04Ar6en5_Ncv0bpER5PRlNYtB7S7Ocr2vScw,243330
8
- sqlglot/generator.py,sha256=E1LjyN49nX9XfK-hysHWvpw7-qtws4xeb85sZi5x3M0,213345
7
+ sqlglot/expressions.py,sha256=rYPkorYfWlBzPxyaodGqIkW-x6RG1gSkVjBkOfkdZiI,243434
8
+ sqlglot/generator.py,sha256=Od0aBsKJph1wG_YhrknJAcAcVvuVIN823iyxA3KPi0Y,213383
9
9
  sqlglot/helper.py,sha256=9nZjFVRBtMKFC3EdzpDQ6jkazFO19po6BF8xHiNGZIo,15111
10
10
  sqlglot/jsonpath.py,sha256=dKdI3PNINNGimmSse2IIv-GbPN_3lXncXh_70QH7Lss,7664
11
11
  sqlglot/lineage.py,sha256=kXBDSErmZZluZx_kkrMj4MPEOAbkvcbX1tbOW7Bpl-U,15303
12
- sqlglot/parser.py,sha256=xWm01SCq3tSHr7WIVz-h2taaf_JW5JvADsNufE8OAEw,324529
12
+ sqlglot/parser.py,sha256=Mqm77jhuF0b3hyuFPgYtLAMPkuslF64Y8iHIOPw3ZWA,324610
13
13
  sqlglot/planner.py,sha256=ql7Li-bWJRcyXzNaZy_n6bQ6B2ZfunEIB8Ztv2xaxq4,14634
14
14
  sqlglot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  sqlglot/schema.py,sha256=13H2qKQs27EKdTpDLOvcNnSTDAUbYNKjWtJs4aQCSOA,20509
16
16
  sqlglot/serde.py,sha256=DQVJ95WrIvhYfe02Ytb4NQug2aMwDCEwpMBW1LKDqzE,2031
17
17
  sqlglot/time.py,sha256=Q62gv6kL40OiRBF6BMESxKJcMVn7ZLNw7sv8H34z5FI,18400
18
18
  sqlglot/tokens.py,sha256=R0B8GQSbQ9GoDc0NlaT5Tc8RjgEOx2IYIkYU5rY8Rg8,48742
19
- sqlglot/transforms.py,sha256=3jpbHeVTLK9hmQi5f3_vmK-5jZB32_ittCkO7poxCs4,40631
19
+ sqlglot/transforms.py,sha256=s96QMtR7rJbcLAU1I_IF1xLNxno6yvEbhERgbS5xmJ4,41164
20
20
  sqlglot/trie.py,sha256=v27uXMrHfqrXlJ6GmeTSMovsB_3o0ctnlKhdNt7W6fI,2245
21
- sqlglot/dialects/__init__.py,sha256=Rcmnv_D8xjCrEPObi4R2_cs3upbfc5OrDKMIJhQTt4k,3513
21
+ sqlglot/dialects/__init__.py,sha256=G-YO1_zIcONWb9LjTjHX_HGzGl9Rm0sA9MX4ok6tpns,3527
22
22
  sqlglot/dialects/athena.py,sha256=gPE9ybRcbd6dVa1mrTFB_eVjsjQG36hErq5EpHyQmXo,6344
23
- sqlglot/dialects/bigquery.py,sha256=Dw_ZOCv_rCDiUqMBJbmkb6b_R2t9ZemiRmvqu9YLBmY,52756
23
+ sqlglot/dialects/bigquery.py,sha256=5s4hSe-PXbjeIlKhAZon-rGq4ZIywYZj1kxx213V748,52862
24
24
  sqlglot/dialects/clickhouse.py,sha256=Dc0aXwEgN8b6coXKM6P8zh3IsyrXjBajNGB-cVhnu1Y,56603
25
- sqlglot/dialects/databricks.py,sha256=8PoaiP8PfiBjpheRiua-rO_HzX2TRUXqc3DnlQ8zYrg,4481
26
- sqlglot/dialects/dialect.py,sha256=-u8403azEMX3F9KrLQnv7xOU6IaHpxL4pJH733oQlqs,68747
25
+ sqlglot/dialects/databricks.py,sha256=mJN2lFpqgH95x3mtry3qWbuRf4q7NV5jbRAOspqclzY,4548
26
+ sqlglot/dialects/dialect.py,sha256=qcpaE4cYO3v2R1cQVonpbrJOybYspnEdXSkXWxDW6d4,68921
27
27
  sqlglot/dialects/doris.py,sha256=eC7Ct-iz7p4Usz659NkelUFhm-GmVolIZy5uaBvgjaA,14397
28
28
  sqlglot/dialects/drill.py,sha256=FOh7_KjPx_77pv0DiHKZog0CcmzqeF9_PEmGnJ1ESSM,5825
29
29
  sqlglot/dialects/druid.py,sha256=kh3snZtneehNOWqs3XcPjsrhNaRbkCQ8E4hHbWJ1fHM,690
30
30
  sqlglot/dialects/duckdb.py,sha256=oGCgK0KjwJcCKy-YOZeiQnEo4v7Zc1r5AK0tCXO2VIc,48005
31
31
  sqlglot/dialects/dune.py,sha256=gALut-fFfN2qMsr8LvZ1NQK3F3W9z2f4PwMvTMXVVVg,375
32
- sqlglot/dialects/fabric.py,sha256=RfRvQq7AVcr7yT30rqsTk-QILmhTJHECXZXMOotmL6I,4104
32
+ sqlglot/dialects/exasol.py,sha256=r2fO9FHfMV1_1M62wBGlNcQ6fHWikO4SBr8eCzxEYEY,2008
33
+ sqlglot/dialects/fabric.py,sha256=IU7aMh2yEuG8eVBAYzXO5pObZBZ4rZSd5UgvkwbCI-E,5277
33
34
  sqlglot/dialects/hive.py,sha256=yKCsVN4R8pIB2Lmx1YGiSR9b8Me3li6rsGuZrKjHTo4,31771
34
35
  sqlglot/dialects/materialize.py,sha256=_DPLPt8YrdQIIXNrGJw1IMcGOoAEJ9NO9X9pDfy4hxs,3494
35
36
  sqlglot/dialects/mysql.py,sha256=prZecn3zeoifZX7l54UuLG64ar7I-or_z9lF-rT8bds,49233
36
37
  sqlglot/dialects/oracle.py,sha256=o6On1cYWFt6TpQYKuzo4kCz5vKb8jQr8WSwc619h3Lg,15967
37
38
  sqlglot/dialects/postgres.py,sha256=KUyMoLkm1_sZKUbdjn6bjXx9xz7sbEMKa-fl5Mzfrsk,31025
38
- sqlglot/dialects/presto.py,sha256=xsbYSc_1-z-jSOsG85z9Pw7pd_V_BX0Dila7KsMsS04,33203
39
+ sqlglot/dialects/presto.py,sha256=dHdPv6tUO-7SAYUWnx5ftKzv6FcRvzBfiYDTlQvL2Cs,33312
39
40
  sqlglot/dialects/prql.py,sha256=fwN-SPEGx-drwf1K0U2MByN-PkW3C_rOgQ3xeJeychg,7908
40
- sqlglot/dialects/redshift.py,sha256=UwfntKCfPpX63G6ow4vjadFpfmfaKrmFOGLoOuWN8Yg,15406
41
+ sqlglot/dialects/redshift.py,sha256=sHhibn2g6_hVRd1XEe8HSQd_ofWkEpzld0odsNQ6X2g,15747
41
42
  sqlglot/dialects/risingwave.py,sha256=hwEOPjMw0ZM_3fjQcBUE00oy6I8V6mzYOOYmcwwS8mw,2898
42
- sqlglot/dialects/snowflake.py,sha256=kpoWQ_w3SJyb605QWSvr-BxBR3pP9tmlDbT4ix8p484,63438
43
+ sqlglot/dialects/snowflake.py,sha256=68I7OjdWXSVnDxJ-ItmXnJd-A1nlND1T6aKNv0nkJlQ,63518
43
44
  sqlglot/dialects/spark.py,sha256=bOUSXUoWtLfWaQ9fIjWaw4zLBJY6N7vxajdMbAxLdOk,8307
44
45
  sqlglot/dialects/spark2.py,sha256=8er7nHDm5Wc57m9AOxKN0sd_DVzbhAL44H_udlFh9O8,14258
45
46
  sqlglot/dialects/sqlite.py,sha256=fwqmopeuoupD_2dh2q6rT3UFxWtFHkskZ1OXAYnPT9Q,12483
@@ -47,7 +48,7 @@ sqlglot/dialects/starrocks.py,sha256=fHNgvq5Nz7dI4QUWCTOO5VDOYjasBxRRlcg9TbY0UZE
47
48
  sqlglot/dialects/tableau.py,sha256=oIawDzUITxGCWaEMB8OaNMPWhbC3U-2y09pYPm4eazc,2190
48
49
  sqlglot/dialects/teradata.py,sha256=xWa-9kSTsT-eM1NePi_oIM1dPHmXW89GLU5Uda3_6Ao,14036
49
50
  sqlglot/dialects/trino.py,sha256=wgLsiX1NQvjGny_rgrU1e2r6kK1LD0KgaSdIDrYmjD0,4285
50
- sqlglot/dialects/tsql.py,sha256=kMa8hYAXp3D2-g4HzkuzHDsWeXU1WgbyZm2sNl2a8rE,54397
51
+ sqlglot/dialects/tsql.py,sha256=dKlGmOmRFDx2MO5YebAAIK3FHorLZfzR0iqtK6xiiX4,54540
51
52
  sqlglot/executor/__init__.py,sha256=FslewzYQtQdDNg_0Ju2UaiP4vo4IMUgkfkmFsYUhcN0,2958
52
53
  sqlglot/executor/context.py,sha256=WJHJdYQCOeVXwLw0uSSrWSc25eBMn5Ix108RCvdsKRQ,3386
53
54
  sqlglot/executor/env.py,sha256=tQhU5PpTBMcxgZIFddFqxWMNPtHN0vOOz72voncY3KY,8276
@@ -70,11 +71,11 @@ sqlglot/optimizer/pushdown_projections.py,sha256=7NoK5NAUVYVhs0YnYyo6WuXfaO-BShS
70
71
  sqlglot/optimizer/qualify.py,sha256=oAPfwub7dEkrlCrsptcJWpLya4BgKhN6M5SwIs_86LY,4002
71
72
  sqlglot/optimizer/qualify_columns.py,sha256=77aScPakXYaiagnoCWk2qwMxlKuRGsFTAK9sOQuR2vY,40872
72
73
  sqlglot/optimizer/qualify_tables.py,sha256=5f5enBAh-bpNB9ewF97W9fx9h1TGXj1Ih5fncvH42sY,6486
73
- sqlglot/optimizer/scope.py,sha256=r-2PaO7-woaIWaWrKC88J9eTgdQardNYQ1rIXXaPr1w,30501
74
+ sqlglot/optimizer/scope.py,sha256=HI3TZ4VWTgM6_x8k5ClA0lA0xidaKv4xgn8iGERJRjk,30824
74
75
  sqlglot/optimizer/simplify.py,sha256=S0Blqg5Mq2KRRWhWz-Eivch9sBjBhg9fRJA6EdBzj2g,50704
75
76
  sqlglot/optimizer/unnest_subqueries.py,sha256=kzWUVDlxs8z9nmRx-8U-pHXPtVZhEIwkKqmKhr2QLvc,10908
76
- sqlglot-26.30.0.dist-info/licenses/LICENSE,sha256=AI3__mHZfOtzY3EluR_pIYBm3_pE7TbVx7qaHxoZ114,1065
77
- sqlglot-26.30.0.dist-info/METADATA,sha256=rIvq32wg6apWdTgqTkYe5mYgGj5XTwMQ8rvqjoTQruI,20732
78
- sqlglot-26.30.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
- sqlglot-26.30.0.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
80
- sqlglot-26.30.0.dist-info/RECORD,,
77
+ sqlglot-26.31.0.dist-info/licenses/LICENSE,sha256=AI3__mHZfOtzY3EluR_pIYBm3_pE7TbVx7qaHxoZ114,1065
78
+ sqlglot-26.31.0.dist-info/METADATA,sha256=OAEEcPh5a0gV2C4sacAbhuXg4cpNWPUXeGS0H6iAGgs,20732
79
+ sqlglot-26.31.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
80
+ sqlglot-26.31.0.dist-info/top_level.txt,sha256=5kRskCGA_gVADF9rSfSzPdLHXqvfMusDYeHePfNY2nQ,8
81
+ sqlglot-26.31.0.dist-info/RECORD,,