sqlglot 28.4.0__py3-none-any.whl → 28.8.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.
Files changed (50) hide show
  1. sqlglot/_version.py +2 -2
  2. sqlglot/dialects/bigquery.py +20 -23
  3. sqlglot/dialects/clickhouse.py +2 -0
  4. sqlglot/dialects/dialect.py +355 -18
  5. sqlglot/dialects/doris.py +38 -90
  6. sqlglot/dialects/druid.py +1 -0
  7. sqlglot/dialects/duckdb.py +1739 -163
  8. sqlglot/dialects/exasol.py +17 -1
  9. sqlglot/dialects/hive.py +27 -2
  10. sqlglot/dialects/mysql.py +103 -11
  11. sqlglot/dialects/oracle.py +38 -1
  12. sqlglot/dialects/postgres.py +142 -33
  13. sqlglot/dialects/presto.py +6 -2
  14. sqlglot/dialects/redshift.py +7 -1
  15. sqlglot/dialects/singlestore.py +13 -3
  16. sqlglot/dialects/snowflake.py +271 -21
  17. sqlglot/dialects/spark.py +25 -0
  18. sqlglot/dialects/spark2.py +4 -3
  19. sqlglot/dialects/starrocks.py +152 -17
  20. sqlglot/dialects/trino.py +1 -0
  21. sqlglot/dialects/tsql.py +5 -0
  22. sqlglot/diff.py +1 -1
  23. sqlglot/expressions.py +239 -47
  24. sqlglot/generator.py +173 -44
  25. sqlglot/optimizer/annotate_types.py +129 -60
  26. sqlglot/optimizer/merge_subqueries.py +13 -2
  27. sqlglot/optimizer/qualify_columns.py +7 -0
  28. sqlglot/optimizer/resolver.py +19 -0
  29. sqlglot/optimizer/scope.py +12 -0
  30. sqlglot/optimizer/unnest_subqueries.py +7 -0
  31. sqlglot/parser.py +251 -58
  32. sqlglot/schema.py +186 -14
  33. sqlglot/tokens.py +36 -6
  34. sqlglot/transforms.py +6 -5
  35. sqlglot/typing/__init__.py +29 -10
  36. sqlglot/typing/bigquery.py +5 -10
  37. sqlglot/typing/duckdb.py +39 -0
  38. sqlglot/typing/hive.py +50 -1
  39. sqlglot/typing/mysql.py +32 -0
  40. sqlglot/typing/presto.py +0 -1
  41. sqlglot/typing/snowflake.py +80 -17
  42. sqlglot/typing/spark.py +29 -0
  43. sqlglot/typing/spark2.py +9 -1
  44. sqlglot/typing/tsql.py +21 -0
  45. {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/METADATA +47 -2
  46. sqlglot-28.8.0.dist-info/RECORD +95 -0
  47. {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/WHEEL +1 -1
  48. sqlglot-28.4.0.dist-info/RECORD +0 -92
  49. {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/licenses/LICENSE +0 -0
  50. {sqlglot-28.4.0.dist-info → sqlglot-28.8.0.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from sqlglot.dialects.dialect import (
16
16
  build_date_delta,
17
17
  no_last_day_sql,
18
18
  DATE_ADD_OR_SUB,
19
+ build_timetostr_or_tochar,
19
20
  )
20
21
  from sqlglot.generator import unsupported_args
21
22
  from sqlglot.helper import seq_get
@@ -319,6 +320,7 @@ class Exasol(Dialect):
319
320
  "ENDIF": TokenType.END,
320
321
  "LONG VARCHAR": TokenType.TEXT,
321
322
  "SEPARATOR": TokenType.SEPARATOR,
323
+ "SYSTIMESTAMP": TokenType.SYSTIMESTAMP,
322
324
  }
323
325
  KEYWORDS.pop("DIV")
324
326
 
@@ -368,7 +370,7 @@ class Exasol(Dialect):
368
370
  "TRUNCATE": _build_trunc,
369
371
  "VAR_POP": exp.VariancePop.from_arg_list,
370
372
  "APPROXIMATE_COUNT_DISTINCT": exp.ApproxDistinct.from_arg_list,
371
- "TO_CHAR": build_formatted_time(exp.ToChar, "exasol"),
373
+ "TO_CHAR": build_timetostr_or_tochar,
372
374
  "TO_DATE": build_formatted_time(exp.TsOrDsToDate, "exasol"),
373
375
  # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/convert_tz.htm
374
376
  "CONVERT_TZ": lambda args: exp.ConvertTimezone(
@@ -387,6 +389,17 @@ class Exasol(Dialect):
387
389
  this=self._match(TokenType.IS) and self._parse_string(),
388
390
  ),
389
391
  }
392
+
393
+ FUNC_TOKENS = {
394
+ *parser.Parser.FUNC_TOKENS,
395
+ TokenType.SYSTIMESTAMP,
396
+ }
397
+
398
+ NO_PAREN_FUNCTIONS = {
399
+ **parser.Parser.NO_PAREN_FUNCTIONS,
400
+ TokenType.SYSTIMESTAMP: exp.Systimestamp,
401
+ }
402
+
390
403
  FUNCTION_PARSERS = {
391
404
  **parser.Parser.FUNCTION_PARSERS,
392
405
  # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/listagg.htm
@@ -438,6 +451,9 @@ class Exasol(Dialect):
438
451
  exp.DataType.Type.DECIMAL128: "DECIMAL",
439
452
  exp.DataType.Type.DECIMAL256: "DECIMAL",
440
453
  exp.DataType.Type.DATETIME: "TIMESTAMP",
454
+ exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
455
+ exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP",
456
+ exp.DataType.Type.TIMESTAMPNTZ: "TIMESTAMP",
441
457
  }
442
458
 
443
459
  def datatype_sql(self, expression: exp.DataType) -> str:
sqlglot/dialects/hive.py CHANGED
@@ -30,6 +30,7 @@ from sqlglot.dialects.dialect import (
30
30
  struct_extract_sql,
31
31
  time_format,
32
32
  timestrtotime_sql,
33
+ trim_sql,
33
34
  unit_to_str,
34
35
  var_map_sql,
35
36
  sequence_sql,
@@ -48,6 +49,10 @@ from sqlglot.generator import unsupported_args
48
49
  from sqlglot.optimizer.annotate_types import TypeAnnotator
49
50
  from sqlglot.typing.hive import EXPRESSION_METADATA
50
51
 
52
+ if t.TYPE_CHECKING:
53
+ from sqlglot._typing import F
54
+
55
+
51
56
  # (FuncType, Multiplier)
52
57
  DATE_DELTA_INTERVAL = {
53
58
  "YEAR": ("ADD_MONTHS", 12),
@@ -315,6 +320,12 @@ class Hive(Dialect):
315
320
  CHANGE_COLUMN_ALTER_SYNTAX = False
316
321
  # Whether the dialect supports using ALTER COLUMN syntax with CHANGE COLUMN.
317
322
 
323
+ FUNCTION_PARSERS = {
324
+ **parser.Parser.FUNCTION_PARSERS,
325
+ "PERCENTILE": lambda self: self._parse_quantile_function(exp.Quantile),
326
+ "PERCENTILE_APPROX": lambda self: self._parse_quantile_function(exp.ApproxQuantile),
327
+ }
328
+
318
329
  FUNCTIONS = {
319
330
  **parser.Parser.FUNCTIONS,
320
331
  "BASE64": exp.ToBase64.from_arg_list,
@@ -345,8 +356,6 @@ class Hive(Dialect):
345
356
  "LAST_VALUE": _build_with_ignore_nulls(exp.LastValue),
346
357
  "MAP": parser.build_var_map,
347
358
  "MONTH": lambda args: exp.Month(this=exp.TsOrDsToDate.from_arg_list(args)),
348
- "PERCENTILE": exp.Quantile.from_arg_list,
349
- "PERCENTILE_APPROX": exp.ApproxQuantile.from_arg_list,
350
359
  "REGEXP_EXTRACT": build_regexp_extract(exp.RegexpExtract),
351
360
  "REGEXP_EXTRACT_ALL": build_regexp_extract(exp.RegexpExtractAll),
352
361
  "SEQUENCE": exp.GenerateSeries.from_arg_list,
@@ -423,6 +432,21 @@ class Hive(Dialect):
423
432
  record_reader=record_reader,
424
433
  )
425
434
 
435
+ def _parse_quantile_function(self, func: t.Type[F]) -> F:
436
+ if self._match(TokenType.DISTINCT):
437
+ first_arg: t.Optional[exp.Expression] = self.expression(
438
+ exp.Distinct, expressions=[self._parse_lambda()]
439
+ )
440
+ else:
441
+ self._match(TokenType.ALL)
442
+ first_arg = self._parse_lambda()
443
+
444
+ args = [first_arg]
445
+ if self._match(TokenType.COMMA):
446
+ args.extend(self._parse_function_args())
447
+
448
+ return func.from_arg_list(args)
449
+
426
450
  def _parse_types(
427
451
  self, check_func: bool = False, schema: bool = False, allow_identifiers: bool = True
428
452
  ) -> t.Optional[exp.Expression]:
@@ -660,6 +684,7 @@ class Hive(Dialect):
660
684
  exp.TsOrDsDiff: _date_diff_sql,
661
685
  exp.TsOrDsToDate: _to_date_sql,
662
686
  exp.TryCast: no_trycast_sql,
687
+ exp.Trim: trim_sql,
663
688
  exp.Unicode: rename_func("ASCII"),
664
689
  exp.UnixToStr: lambda self, e: self.func(
665
690
  "FROM_UNIXTIME", e.this, time_format("hive")(self, e)
sqlglot/dialects/mysql.py CHANGED
@@ -30,6 +30,7 @@ from sqlglot.dialects.dialect import (
30
30
  from sqlglot.generator import unsupported_args
31
31
  from sqlglot.helper import seq_get
32
32
  from sqlglot.tokens import TokenType
33
+ from sqlglot.typing.mysql import EXPRESSION_METADATA
33
34
 
34
35
 
35
36
  def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[MySQL.Parser], exp.Show]:
@@ -164,6 +165,9 @@ class MySQL(Dialect):
164
165
  SUPPORTS_SEMI_ANTI_JOIN = False
165
166
  SAFE_DIVISION = True
166
167
  SAFE_TO_ELIMINATE_DOUBLE_NEGATION = False
168
+ LEAST_GREATEST_IGNORES_NULLS = False
169
+
170
+ EXPRESSION_METADATA = EXPRESSION_METADATA.copy()
167
171
 
168
172
  # https://prestodb.io/docs/current/functions/datetime.html#mysql-date-functions
169
173
  TIME_MAPPING = {
@@ -292,6 +296,7 @@ class MySQL(Dialect):
292
296
  TokenType.MOD,
293
297
  TokenType.SCHEMA,
294
298
  TokenType.VALUES,
299
+ TokenType.CHARACTER_SET,
295
300
  }
296
301
 
297
302
  CONJUNCTION = {
@@ -372,11 +377,6 @@ class MySQL(Dialect):
372
377
 
373
378
  FUNCTION_PARSERS = {
374
379
  **parser.Parser.FUNCTION_PARSERS,
375
- "CHAR": lambda self: self.expression(
376
- exp.Chr,
377
- expressions=self._parse_csv(self._parse_assignment),
378
- charset=self._match(TokenType.USING) and self._parse_var(),
379
- ),
380
380
  "GROUP_CONCAT": lambda self: self._parse_group_concat(),
381
381
  # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values
382
382
  "VALUES": lambda self: self.expression(
@@ -449,6 +449,7 @@ class MySQL(Dialect):
449
449
  PROPERTY_PARSERS = {
450
450
  **parser.Parser.PROPERTY_PARSERS,
451
451
  "LOCK": lambda self: self._parse_property_assignment(exp.LockProperty),
452
+ "PARTITION BY": lambda self: self._parse_partition_property(),
452
453
  }
453
454
 
454
455
  SET_PARSERS = {
@@ -732,6 +733,63 @@ class MySQL(Dialect):
732
733
 
733
734
  return self.expression(exp.AlterIndex, this=index, visible=visible)
734
735
 
736
+ def _parse_partition_property(
737
+ self,
738
+ ) -> t.Optional[exp.Expression] | t.List[exp.Expression]:
739
+ partition_cls: t.Optional[t.Type[exp.Expression]] = None
740
+ value_parser = None
741
+
742
+ if self._match_text_seq("RANGE"):
743
+ partition_cls = exp.PartitionByRangeProperty
744
+ value_parser = self._parse_partition_range_value
745
+ elif self._match_text_seq("LIST"):
746
+ partition_cls = exp.PartitionByListProperty
747
+ value_parser = self._parse_partition_list_value
748
+
749
+ if not partition_cls or not value_parser:
750
+ return None
751
+
752
+ partition_expressions = self._parse_wrapped_csv(self._parse_assignment)
753
+
754
+ # For Doris and Starrocks
755
+ if not self._match_text_seq("(", "PARTITION", advance=False):
756
+ return partition_expressions
757
+
758
+ create_expressions = self._parse_wrapped_csv(value_parser)
759
+
760
+ return self.expression(
761
+ partition_cls,
762
+ partition_expressions=partition_expressions,
763
+ create_expressions=create_expressions,
764
+ )
765
+
766
+ def _parse_partition_range_value(self) -> t.Optional[exp.Expression]:
767
+ self._match_text_seq("PARTITION")
768
+ name = self._parse_id_var()
769
+
770
+ if not self._match_text_seq("VALUES", "LESS", "THAN"):
771
+ return name
772
+
773
+ values = self._parse_wrapped_csv(self._parse_expression)
774
+
775
+ if (
776
+ len(values) == 1
777
+ and isinstance(values[0], exp.Column)
778
+ and values[0].name.upper() == "MAXVALUE"
779
+ ):
780
+ values = [exp.var("MAXVALUE")]
781
+
782
+ part_range = self.expression(exp.PartitionRange, this=name, expressions=values)
783
+ return self.expression(exp.Partition, expressions=[part_range])
784
+
785
+ def _parse_partition_list_value(self) -> exp.Partition:
786
+ self._match_text_seq("PARTITION")
787
+ name = self._parse_id_var()
788
+ self._match_text_seq("VALUES", "IN")
789
+ values = self._parse_wrapped_csv(self._parse_expression)
790
+ part_list = self.expression(exp.PartitionList, this=name, expressions=values)
791
+ return self.expression(exp.Partition, expressions=[part_list])
792
+
735
793
  class Generator(generator.Generator):
736
794
  INTERVAL_ALLOWS_PLURAL_FORM = False
737
795
  LOCKING_READS_SUPPORTED = True
@@ -752,6 +810,7 @@ class MySQL(Dialect):
752
810
  WRAP_DERIVED_VALUES = False
753
811
  VARCHAR_REQUIRES_SIZE = True
754
812
  SUPPORTS_MEDIAN = False
813
+ UPDATE_STATEMENT_SUPPORTS_FROM = False
755
814
 
756
815
  TRANSFORMS = {
757
816
  **generator.Generator.TRANSFORMS,
@@ -760,6 +819,7 @@ class MySQL(Dialect):
760
819
  exp.BitwiseOrAgg: rename_func("BIT_OR"),
761
820
  exp.BitwiseXorAgg: rename_func("BIT_XOR"),
762
821
  exp.BitwiseCount: rename_func("BIT_COUNT"),
822
+ exp.Chr: lambda self, e: self.chr_sql(e, "CHAR"),
763
823
  exp.CurrentDate: no_paren_current_date_sql,
764
824
  exp.DateDiff: _remove_ts_or_ds_to_date(
765
825
  lambda self, e: self.func("DATEDIFF", e.this, e.expression), ("this", "expression")
@@ -869,6 +929,9 @@ class MySQL(Dialect):
869
929
  **generator.Generator.PROPERTIES_LOCATION,
870
930
  exp.TransientProperty: exp.Properties.Location.UNSUPPORTED,
871
931
  exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
932
+ exp.PartitionedByProperty: exp.Properties.Location.UNSUPPORTED,
933
+ exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA,
934
+ exp.PartitionByListProperty: exp.Properties.Location.POST_SCHEMA,
872
935
  }
873
936
 
874
937
  LIMIT_FETCH = "LIMIT"
@@ -1281,6 +1344,12 @@ class MySQL(Dialect):
1281
1344
 
1282
1345
  return f"SHOW{full}{global_}{this}{json}{target}{for_table}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}{for_group}{for_user}{for_role}{into_outfile}"
1283
1346
 
1347
+ def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
1348
+ """To avoid TO keyword in ALTER ... RENAME statements.
1349
+ It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks.
1350
+ """
1351
+ return super().alterrename_sql(expression, include_to=False)
1352
+
1284
1353
  def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
1285
1354
  dtype = self.sql(expression, "dtype")
1286
1355
  if not dtype:
@@ -1301,12 +1370,6 @@ class MySQL(Dialect):
1301
1370
  return f" LIMIT {limit_offset}"
1302
1371
  return ""
1303
1372
 
1304
- def chr_sql(self, expression: exp.Chr) -> str:
1305
- this = self.expressions(sqls=[expression.this] + expression.expressions)
1306
- charset = expression.args.get("charset")
1307
- using = f" USING {self.sql(charset)}" if charset else ""
1308
- return f"CHAR({this}{using})"
1309
-
1310
1373
  def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str:
1311
1374
  unit = expression.args.get("unit")
1312
1375
 
@@ -1342,3 +1405,32 @@ class MySQL(Dialect):
1342
1405
  @unsupported_args("this")
1343
1406
  def currentschema_sql(self, expression: exp.CurrentSchema) -> str:
1344
1407
  return self.func("SCHEMA")
1408
+
1409
+ def partition_sql(self, expression: exp.Partition) -> str:
1410
+ parent = expression.parent
1411
+ if isinstance(parent, (exp.PartitionByRangeProperty, exp.PartitionByListProperty)):
1412
+ return self.expressions(expression, flat=True)
1413
+ return super().partition_sql(expression)
1414
+
1415
+ def _partition_by_sql(
1416
+ self, expression: exp.PartitionByRangeProperty | exp.PartitionByListProperty, kind: str
1417
+ ) -> str:
1418
+ partitions = self.expressions(expression, key="partition_expressions", flat=True)
1419
+ create = self.expressions(expression, key="create_expressions", flat=True)
1420
+ return f"PARTITION BY {kind} ({partitions}) ({create})"
1421
+
1422
+ def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
1423
+ return self._partition_by_sql(expression, "RANGE")
1424
+
1425
+ def partitionbylistproperty_sql(self, expression: exp.PartitionByListProperty) -> str:
1426
+ return self._partition_by_sql(expression, "LIST")
1427
+
1428
+ def partitionlist_sql(self, expression: exp.PartitionList) -> str:
1429
+ name = self.sql(expression, "this")
1430
+ values = self.expressions(expression, flat=True)
1431
+ return f"PARTITION {name} VALUES IN ({values})"
1432
+
1433
+ def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
1434
+ name = self.sql(expression, "this")
1435
+ values = self.expressions(expression, flat=True)
1436
+ return f"PARTITION {name} VALUES LESS THAN ({values})"
@@ -108,6 +108,7 @@ class Oracle(Dialect):
108
108
  "START": TokenType.BEGIN,
109
109
  "TOP": TokenType.TOP,
110
110
  "VARCHAR2": TokenType.VARCHAR,
111
+ "SYSTIMESTAMP": TokenType.SYSTIMESTAMP,
111
112
  }
112
113
 
113
114
  class Parser(parser.Parser):
@@ -139,6 +140,11 @@ class Oracle(Dialect):
139
140
  "DBMS_RANDOM": lambda self: self._parse_dbms_random(),
140
141
  }
141
142
 
143
+ NO_PAREN_FUNCTIONS = {
144
+ **parser.Parser.NO_PAREN_FUNCTIONS,
145
+ TokenType.SYSTIMESTAMP: exp.Systimestamp,
146
+ }
147
+
142
148
  FUNCTION_PARSERS: t.Dict[str, t.Callable] = {
143
149
  **parser.Parser.FUNCTION_PARSERS,
144
150
  "JSON_ARRAY": lambda self: self._parse_json_array(
@@ -172,7 +178,11 @@ class Oracle(Dialect):
172
178
  TYPE_LITERAL_PARSERS = {
173
179
  exp.DataType.Type.DATE: lambda self, this, _: self.expression(
174
180
  exp.DateStrToDate, this=this
175
- )
181
+ ),
182
+ # https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/NLS_TIMESTAMP_FORMAT.html
183
+ exp.DataType.Type.TIMESTAMP: lambda self, this, _: _build_to_timestamp(
184
+ [this, '"%Y-%m-%d %H:%M:%S.%f"']
185
+ ),
176
186
  }
177
187
 
178
188
  # SELECT UNIQUE .. is old-style Oracle syntax for SELECT DISTINCT ..
@@ -275,6 +285,22 @@ class Oracle(Dialect):
275
285
  def _parse_connect_with_prior(self):
276
286
  return self._parse_assignment()
277
287
 
288
+ def _parse_column_ops(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]:
289
+ this = super()._parse_column_ops(this)
290
+
291
+ if not this:
292
+ return this
293
+
294
+ index = self._index
295
+
296
+ # https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/Interval-Expressions.html
297
+ interval_span = self._parse_interval_span(this)
298
+ if isinstance(interval_span.args.get("unit"), exp.IntervalSpan):
299
+ return interval_span
300
+
301
+ self._retreat(index)
302
+ return this
303
+
278
304
  def _parse_insert_table(self) -> t.Optional[exp.Expression]:
279
305
  # Oracle does not use AS for INSERT INTO alias
280
306
  # https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/INSERT.html
@@ -368,6 +394,7 @@ class Oracle(Dialect):
368
394
  e: f"TO_DATE('1970-01-01', 'YYYY-MM-DD') + ({self.sql(e, 'this')} / 86400)",
369
395
  exp.UtcTimestamp: rename_func("UTC_TIMESTAMP"),
370
396
  exp.UtcTime: rename_func("UTC_TIME"),
397
+ exp.Systimestamp: lambda self, e: "SYSTIMESTAMP",
371
398
  }
372
399
 
373
400
  PROPERTIES_LOCATION = {
@@ -420,3 +447,13 @@ class Oracle(Dialect):
420
447
 
421
448
  def isascii_sql(self, expression: exp.IsAscii) -> str:
422
449
  return f"NVL(REGEXP_LIKE({self.sql(expression.this)}, '^[' || CHR(1) || '-' || CHR(127) || ']*$'), TRUE)"
450
+
451
+ def interval_sql(self, expression: exp.Interval) -> str:
452
+ return f"{'INTERVAL ' if isinstance(expression.this, exp.Literal) else ''}{self.sql(expression, 'this')} {self.sql(expression, 'unit')}"
453
+
454
+ def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
455
+ param_constraint = expression.find(exp.InOutColumnConstraint)
456
+ if param_constraint:
457
+ sep = f" {self.sql(param_constraint)} "
458
+ param_constraint.pop()
459
+ return super().columndef_sql(expression, sep)