sqlglot.dialects.postgres
1from __future__ import annotations 2 3import typing as t 4 5from sqlglot import exp, generator, parser, tokens, transforms 6from sqlglot.dialects.dialect import ( 7 DATE_ADD_OR_SUB, 8 Dialect, 9 JSON_EXTRACT_TYPE, 10 any_value_to_max_sql, 11 array_append_sql, 12 array_concat_sql, 13 binary_from_function, 14 bool_xor_sql, 15 datestrtodate_sql, 16 build_formatted_time, 17 filter_array_using_unnest, 18 getbit_sql, 19 inline_array_sql, 20 json_extract_segments, 21 json_path_key_only_name, 22 max_or_greatest, 23 merge_without_target_sql, 24 min_or_least, 25 no_last_day_sql, 26 no_map_from_entries_sql, 27 no_paren_current_date_sql, 28 no_pivot_sql, 29 no_trycast_sql, 30 build_json_extract_path, 31 build_timestamp_trunc, 32 rename_func, 33 sha256_sql, 34 struct_extract_sql, 35 timestamptrunc_sql, 36 timestrtotime_sql, 37 trim_sql, 38 ts_or_ds_add_cast, 39 strposition_sql, 40 count_if_to_sum, 41 groupconcat_sql, 42 regexp_replace_global_modifier, 43 sha2_digest_sql, 44) 45from sqlglot.generator import unsupported_args 46from sqlglot.helper import is_int, seq_get 47from sqlglot.parser import binary_range_parser 48from sqlglot.tokens import TokenType 49 50if t.TYPE_CHECKING: 51 from sqlglot.dialects.dialect import DialectType 52 53 54DATE_DIFF_FACTOR = { 55 "MICROSECOND": " * 1000000", 56 "MILLISECOND": " * 1000", 57 "SECOND": "", 58 "MINUTE": " / 60", 59 "HOUR": " / 3600", 60 "DAY": " / 86400", 61} 62 63 64def _date_add_sql(kind: str) -> t.Callable[[Postgres.Generator, DATE_ADD_OR_SUB], str]: 65 def func(self: Postgres.Generator, expression: DATE_ADD_OR_SUB) -> str: 66 if isinstance(expression, exp.TsOrDsAdd): 67 expression = ts_or_ds_add_cast(expression) 68 69 this = self.sql(expression, "this") 70 unit = expression.args.get("unit") 71 72 e = self._simplify_unless_literal(expression.expression) 73 if isinstance(e, exp.Literal): 74 e.set("is_string", True) 75 elif e.is_number: 76 e = exp.Literal.string(e.to_py()) 77 else: 78 self.unsupported("Cannot add non literal") 79 80 return f"{this} {kind} {self.sql(exp.Interval(this=e, unit=unit))}" 81 82 return func 83 84 85def _date_diff_sql(self: Postgres.Generator, expression: exp.DateDiff) -> str: 86 unit = expression.text("unit").upper() 87 factor = DATE_DIFF_FACTOR.get(unit) 88 89 end = f"CAST({self.sql(expression, 'this')} AS TIMESTAMP)" 90 start = f"CAST({self.sql(expression, 'expression')} AS TIMESTAMP)" 91 92 if factor is not None: 93 return f"CAST(EXTRACT(epoch FROM {end} - {start}){factor} AS BIGINT)" 94 95 age = f"AGE({end}, {start})" 96 97 if unit == "WEEK": 98 unit = f"EXTRACT(days FROM ({end} - {start})) / 7" 99 elif unit == "MONTH": 100 unit = f"EXTRACT(year FROM {age}) * 12 + EXTRACT(month FROM {age})" 101 elif unit == "QUARTER": 102 unit = f"EXTRACT(year FROM {age}) * 4 + EXTRACT(month FROM {age}) / 3" 103 elif unit == "YEAR": 104 unit = f"EXTRACT(year FROM {age})" 105 else: 106 unit = age 107 108 return f"CAST({unit} AS BIGINT)" 109 110 111def _substring_sql(self: Postgres.Generator, expression: exp.Substring) -> str: 112 this = self.sql(expression, "this") 113 start = self.sql(expression, "start") 114 length = self.sql(expression, "length") 115 116 from_part = f" FROM {start}" if start else "" 117 for_part = f" FOR {length}" if length else "" 118 119 return f"SUBSTRING({this}{from_part}{for_part})" 120 121 122def _auto_increment_to_serial(expression: exp.Expression) -> exp.Expression: 123 auto = expression.find(exp.AutoIncrementColumnConstraint) 124 125 if auto: 126 expression.args["constraints"].remove(auto.parent) 127 kind = expression.args["kind"] 128 129 if kind.this == exp.DataType.Type.INT: 130 kind.replace(exp.DataType(this=exp.DataType.Type.SERIAL)) 131 elif kind.this == exp.DataType.Type.SMALLINT: 132 kind.replace(exp.DataType(this=exp.DataType.Type.SMALLSERIAL)) 133 elif kind.this == exp.DataType.Type.BIGINT: 134 kind.replace(exp.DataType(this=exp.DataType.Type.BIGSERIAL)) 135 136 return expression 137 138 139def _serial_to_generated(expression: exp.Expression) -> exp.Expression: 140 if not isinstance(expression, exp.ColumnDef): 141 return expression 142 kind = expression.kind 143 if not kind: 144 return expression 145 146 if kind.this == exp.DataType.Type.SERIAL: 147 data_type = exp.DataType(this=exp.DataType.Type.INT) 148 elif kind.this == exp.DataType.Type.SMALLSERIAL: 149 data_type = exp.DataType(this=exp.DataType.Type.SMALLINT) 150 elif kind.this == exp.DataType.Type.BIGSERIAL: 151 data_type = exp.DataType(this=exp.DataType.Type.BIGINT) 152 else: 153 data_type = None 154 155 if data_type: 156 expression.args["kind"].replace(data_type) 157 constraints = expression.args["constraints"] 158 generated = exp.ColumnConstraint(kind=exp.GeneratedAsIdentityColumnConstraint(this=False)) 159 notnull = exp.ColumnConstraint(kind=exp.NotNullColumnConstraint()) 160 161 if notnull not in constraints: 162 constraints.insert(0, notnull) 163 if generated not in constraints: 164 constraints.insert(0, generated) 165 166 return expression 167 168 169def _build_generate_series(args: t.List) -> exp.ExplodingGenerateSeries: 170 # The goal is to convert step values like '1 day' or INTERVAL '1 day' into INTERVAL '1' day 171 # Note: postgres allows calls with just two arguments -- the "step" argument defaults to 1 172 step = seq_get(args, 2) 173 if step is not None: 174 if step.is_string: 175 args[2] = exp.to_interval(step.this) 176 elif isinstance(step, exp.Interval) and not step.args.get("unit"): 177 args[2] = exp.to_interval(step.this.this) 178 179 return exp.ExplodingGenerateSeries.from_arg_list(args) 180 181 182def _build_to_timestamp(args: t.List) -> exp.UnixToTime | exp.StrToTime: 183 # TO_TIMESTAMP accepts either a single double argument or (text, text) 184 if len(args) == 1: 185 # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TABLE 186 return exp.UnixToTime.from_arg_list(args) 187 188 # https://www.postgresql.org/docs/current/functions-formatting.html 189 return build_formatted_time(exp.StrToTime, "postgres")(args) 190 191 192def _json_extract_sql( 193 name: str, op: str 194) -> t.Callable[[Postgres.Generator, JSON_EXTRACT_TYPE], str]: 195 def _generate(self: Postgres.Generator, expression: JSON_EXTRACT_TYPE) -> str: 196 if expression.args.get("only_json_types"): 197 return json_extract_segments(name, quoted_index=False, op=op)(self, expression) 198 return json_extract_segments(name)(self, expression) 199 200 return _generate 201 202 203def _build_regexp_replace(args: t.List, dialect: DialectType = None) -> exp.RegexpReplace: 204 # The signature of REGEXP_REPLACE is: 205 # regexp_replace(source, pattern, replacement [, start [, N ]] [, flags ]) 206 # 207 # Any one of `start`, `N` and `flags` can be column references, meaning that 208 # unless we can statically see that the last argument is a non-integer string 209 # (eg. not '0'), then it's not possible to construct the correct AST 210 regexp_replace = None 211 if len(args) > 3: 212 last = args[-1] 213 if not is_int(last.name): 214 if not last.type or last.is_type(exp.DataType.Type.UNKNOWN, exp.DataType.Type.NULL): 215 from sqlglot.optimizer.annotate_types import annotate_types 216 217 last = annotate_types(last, dialect=dialect) 218 219 if last.is_type(*exp.DataType.TEXT_TYPES): 220 regexp_replace = exp.RegexpReplace.from_arg_list(args[:-1]) 221 regexp_replace.set("modifiers", last) 222 223 regexp_replace = regexp_replace or exp.RegexpReplace.from_arg_list(args) 224 regexp_replace.set("single_replace", True) 225 return regexp_replace 226 227 228def _unix_to_time_sql(self: Postgres.Generator, expression: exp.UnixToTime) -> str: 229 scale = expression.args.get("scale") 230 timestamp = expression.this 231 232 if scale in (None, exp.UnixToTime.SECONDS): 233 return self.func("TO_TIMESTAMP", timestamp, self.format_time(expression)) 234 235 return self.func( 236 "TO_TIMESTAMP", 237 exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)), 238 self.format_time(expression), 239 ) 240 241 242def _build_levenshtein_less_equal(args: t.List) -> exp.Levenshtein: 243 # Postgres has two signatures for levenshtein_less_equal function, but in both cases 244 # max_dist is the last argument 245 # levenshtein_less_equal(source, target, ins_cost, del_cost, sub_cost, max_d) 246 # levenshtein_less_equal(source, target, max_d) 247 max_dist = args.pop() 248 249 return exp.Levenshtein( 250 this=seq_get(args, 0), 251 expression=seq_get(args, 1), 252 ins_cost=seq_get(args, 2), 253 del_cost=seq_get(args, 3), 254 sub_cost=seq_get(args, 4), 255 max_dist=max_dist, 256 ) 257 258 259def _levenshtein_sql(self: Postgres.Generator, expression: exp.Levenshtein) -> str: 260 name = "LEVENSHTEIN_LESS_EQUAL" if expression.args.get("max_dist") else "LEVENSHTEIN" 261 262 return rename_func(name)(self, expression) 263 264 265def _versioned_anyvalue_sql(self: Postgres.Generator, expression: exp.AnyValue) -> str: 266 # https://www.postgresql.org/docs/16/functions-aggregate.html 267 # https://www.postgresql.org/about/featurematrix/ 268 if self.dialect.version < (16,): 269 return any_value_to_max_sql(self, expression) 270 271 return rename_func("ANY_VALUE")(self, expression) 272 273 274def _round_sql(self: Postgres.Generator, expression: exp.Round) -> str: 275 this = self.sql(expression, "this") 276 decimals = self.sql(expression, "decimals") 277 278 if not decimals: 279 return self.func("ROUND", this) 280 281 if not expression.type: 282 from sqlglot.optimizer.annotate_types import annotate_types 283 284 expression = annotate_types(expression, dialect=self.dialect) 285 286 # ROUND(double precision, integer) is not permitted in Postgres 287 # so it's necessary to cast to decimal before rounding. 288 if expression.this.is_type(exp.DataType.Type.DOUBLE): 289 decimal_type = exp.DataType.build( 290 exp.DataType.Type.DECIMAL, expressions=expression.expressions 291 ) 292 this = self.sql(exp.Cast(this=this, to=decimal_type)) 293 294 return self.func("ROUND", this, decimals) 295 296 297class Postgres(Dialect): 298 INDEX_OFFSET = 1 299 TYPED_DIVISION = True 300 CONCAT_COALESCE = True 301 NULL_ORDERING = "nulls_are_large" 302 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 303 TABLESAMPLE_SIZE_IS_PERCENT = True 304 TABLES_REFERENCEABLE_AS_COLUMNS = True 305 306 DEFAULT_FUNCTIONS_COLUMN_NAMES = { 307 exp.ExplodingGenerateSeries: "generate_series", 308 } 309 310 TIME_MAPPING = { 311 "d": "%u", # 1-based day of week 312 "D": "%u", # 1-based day of week 313 "dd": "%d", # day of month 314 "DD": "%d", # day of month 315 "ddd": "%j", # zero padded day of year 316 "DDD": "%j", # zero padded day of year 317 "FMDD": "%-d", # - is no leading zero for Python; same for FM in postgres 318 "FMDDD": "%-j", # day of year 319 "FMHH12": "%-I", # 9 320 "FMHH24": "%-H", # 9 321 "FMMI": "%-M", # Minute 322 "FMMM": "%-m", # 1 323 "FMSS": "%-S", # Second 324 "HH12": "%I", # 09 325 "HH24": "%H", # 09 326 "mi": "%M", # zero padded minute 327 "MI": "%M", # zero padded minute 328 "mm": "%m", # 01 329 "MM": "%m", # 01 330 "OF": "%z", # utc offset 331 "ss": "%S", # zero padded second 332 "SS": "%S", # zero padded second 333 "TMDay": "%A", # TM is locale dependent 334 "TMDy": "%a", 335 "TMMon": "%b", # Sep 336 "TMMonth": "%B", # September 337 "TZ": "%Z", # uppercase timezone name 338 "US": "%f", # zero padded microsecond 339 "ww": "%U", # 1-based week of year 340 "WW": "%U", # 1-based week of year 341 "yy": "%y", # 15 342 "YY": "%y", # 15 343 "yyyy": "%Y", # 2015 344 "YYYY": "%Y", # 2015 345 } 346 347 class Tokenizer(tokens.Tokenizer): 348 BIT_STRINGS = [("b'", "'"), ("B'", "'")] 349 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 350 BYTE_STRINGS = [("e'", "'"), ("E'", "'")] 351 BYTE_STRING_ESCAPES = ["'", "\\"] 352 HEREDOC_STRINGS = ["$"] 353 354 HEREDOC_TAG_IS_IDENTIFIER = True 355 HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER 356 357 KEYWORDS = { 358 **tokens.Tokenizer.KEYWORDS, 359 "~": TokenType.RLIKE, 360 "@@": TokenType.DAT, 361 "@>": TokenType.AT_GT, 362 "<@": TokenType.LT_AT, 363 "?&": TokenType.QMARK_AMP, 364 "?|": TokenType.QMARK_PIPE, 365 "#-": TokenType.HASH_DASH, 366 "|/": TokenType.PIPE_SLASH, 367 "||/": TokenType.DPIPE_SLASH, 368 "BEGIN": TokenType.BEGIN, 369 "BIGSERIAL": TokenType.BIGSERIAL, 370 "CONSTRAINT TRIGGER": TokenType.COMMAND, 371 "CSTRING": TokenType.PSEUDO_TYPE, 372 "DECLARE": TokenType.COMMAND, 373 "DO": TokenType.COMMAND, 374 "EXEC": TokenType.COMMAND, 375 "HSTORE": TokenType.HSTORE, 376 "INT8": TokenType.BIGINT, 377 "MONEY": TokenType.MONEY, 378 "NAME": TokenType.NAME, 379 "OID": TokenType.OBJECT_IDENTIFIER, 380 "ONLY": TokenType.ONLY, 381 "POINT": TokenType.POINT, 382 "REFRESH": TokenType.COMMAND, 383 "REINDEX": TokenType.COMMAND, 384 "RESET": TokenType.COMMAND, 385 "SERIAL": TokenType.SERIAL, 386 "SMALLSERIAL": TokenType.SMALLSERIAL, 387 "TEMP": TokenType.TEMPORARY, 388 "REGCLASS": TokenType.OBJECT_IDENTIFIER, 389 "REGCOLLATION": TokenType.OBJECT_IDENTIFIER, 390 "REGCONFIG": TokenType.OBJECT_IDENTIFIER, 391 "REGDICTIONARY": TokenType.OBJECT_IDENTIFIER, 392 "REGNAMESPACE": TokenType.OBJECT_IDENTIFIER, 393 "REGOPER": TokenType.OBJECT_IDENTIFIER, 394 "REGOPERATOR": TokenType.OBJECT_IDENTIFIER, 395 "REGPROC": TokenType.OBJECT_IDENTIFIER, 396 "REGPROCEDURE": TokenType.OBJECT_IDENTIFIER, 397 "REGROLE": TokenType.OBJECT_IDENTIFIER, 398 "REGTYPE": TokenType.OBJECT_IDENTIFIER, 399 "FLOAT": TokenType.DOUBLE, 400 "XML": TokenType.XML, 401 "VARIADIC": TokenType.VARIADIC, 402 "INOUT": TokenType.INOUT, 403 } 404 KEYWORDS.pop("/*+") 405 KEYWORDS.pop("DIV") 406 407 SINGLE_TOKENS = { 408 **tokens.Tokenizer.SINGLE_TOKENS, 409 "$": TokenType.HEREDOC_STRING, 410 } 411 412 VAR_SINGLE_TOKENS = {"$"} 413 414 class Parser(parser.Parser): 415 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = True 416 417 PROPERTY_PARSERS = { 418 **parser.Parser.PROPERTY_PARSERS, 419 "SET": lambda self: self.expression(exp.SetConfigProperty, this=self._parse_set()), 420 } 421 PROPERTY_PARSERS.pop("INPUT") 422 423 PLACEHOLDER_PARSERS = { 424 **parser.Parser.PLACEHOLDER_PARSERS, 425 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder, jdbc=True), 426 TokenType.MOD: lambda self: self._parse_query_parameter(), 427 } 428 429 FUNCTIONS = { 430 **parser.Parser.FUNCTIONS, 431 "ARRAY_PREPEND": lambda args: exp.ArrayPrepend( 432 this=seq_get(args, 1), expression=seq_get(args, 0) 433 ), 434 "BIT_AND": exp.BitwiseAndAgg.from_arg_list, 435 "BIT_OR": exp.BitwiseOrAgg.from_arg_list, 436 "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, 437 "DATE_TRUNC": build_timestamp_trunc, 438 "DIV": lambda args: exp.cast( 439 binary_from_function(exp.IntDiv)(args), exp.DataType.Type.DECIMAL 440 ), 441 "GENERATE_SERIES": _build_generate_series, 442 "GET_BIT": lambda args: exp.Getbit( 443 this=seq_get(args, 0), expression=seq_get(args, 1), zero_is_msb=True 444 ), 445 "JSON_EXTRACT_PATH": build_json_extract_path(exp.JSONExtract), 446 "JSON_EXTRACT_PATH_TEXT": build_json_extract_path(exp.JSONExtractScalar), 447 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), encoding=seq_get(args, 1)), 448 "MAKE_TIME": exp.TimeFromParts.from_arg_list, 449 "MAKE_TIMESTAMP": exp.TimestampFromParts.from_arg_list, 450 "NOW": exp.CurrentTimestamp.from_arg_list, 451 "REGEXP_REPLACE": _build_regexp_replace, 452 "TO_CHAR": build_formatted_time(exp.TimeToStr, "postgres"), 453 "TO_DATE": build_formatted_time(exp.StrToDate, "postgres"), 454 "TO_TIMESTAMP": _build_to_timestamp, 455 "UNNEST": exp.Explode.from_arg_list, 456 "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)), 457 "SHA384": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(384)), 458 "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)), 459 "LEVENSHTEIN_LESS_EQUAL": _build_levenshtein_less_equal, 460 "JSON_OBJECT_AGG": lambda args: exp.JSONObjectAgg(expressions=args), 461 "JSONB_OBJECT_AGG": exp.JSONBObjectAgg.from_arg_list, 462 "WIDTH_BUCKET": lambda args: exp.WidthBucket( 463 this=seq_get(args, 0), threshold=seq_get(args, 1) 464 ) 465 if len(args) == 2 466 else exp.WidthBucket.from_arg_list(args), 467 } 468 469 NO_PAREN_FUNCTION_PARSERS = { 470 **parser.Parser.NO_PAREN_FUNCTION_PARSERS, 471 "VARIADIC": lambda self: self.expression(exp.Variadic, this=self._parse_bitwise()), 472 } 473 474 NO_PAREN_FUNCTIONS = { 475 **parser.Parser.NO_PAREN_FUNCTIONS, 476 TokenType.CURRENT_SCHEMA: exp.CurrentSchema, 477 } 478 479 FUNCTION_PARSERS = { 480 **parser.Parser.FUNCTION_PARSERS, 481 "DATE_PART": lambda self: self._parse_date_part(), 482 "JSON_AGG": lambda self: self.expression( 483 exp.JSONArrayAgg, 484 this=self._parse_lambda(), 485 order=self._parse_order(), 486 ), 487 "JSONB_EXISTS": lambda self: self._parse_jsonb_exists(), 488 } 489 490 BITWISE = { 491 **parser.Parser.BITWISE, 492 TokenType.HASH: exp.BitwiseXor, 493 } 494 495 EXPONENT = { 496 TokenType.CARET: exp.Pow, 497 } 498 499 RANGE_PARSERS = { 500 **parser.Parser.RANGE_PARSERS, 501 TokenType.DAMP: binary_range_parser(exp.ArrayOverlaps), 502 TokenType.DAT: lambda self, this: self.expression( 503 exp.MatchAgainst, this=self._parse_bitwise(), expressions=[this] 504 ), 505 } 506 507 STATEMENT_PARSERS = { 508 **parser.Parser.STATEMENT_PARSERS, 509 TokenType.END: lambda self: self._parse_commit_or_rollback(), 510 } 511 512 UNARY_PARSERS = { 513 **parser.Parser.UNARY_PARSERS, 514 # The `~` token is remapped from TILDE to RLIKE in Postgres due to the binary REGEXP LIKE operator 515 TokenType.RLIKE: lambda self: self.expression(exp.BitwiseNot, this=self._parse_unary()), 516 } 517 518 JSON_ARROWS_REQUIRE_JSON_TYPE = True 519 520 COLUMN_OPERATORS = { 521 **parser.Parser.COLUMN_OPERATORS, 522 TokenType.ARROW: lambda self, this, path: self.validate_expression( 523 build_json_extract_path( 524 exp.JSONExtract, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE 525 )([this, path]) 526 ), 527 TokenType.DARROW: lambda self, this, path: self.validate_expression( 528 build_json_extract_path( 529 exp.JSONExtractScalar, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE 530 )([this, path]) 531 ), 532 } 533 534 ARG_MODE_TOKENS = {TokenType.IN, TokenType.OUT, TokenType.INOUT, TokenType.VARIADIC} 535 536 def _parse_parameter_mode(self) -> t.Optional[TokenType]: 537 """ 538 Parse PostgreSQL function parameter mode (IN, OUT, INOUT, VARIADIC). 539 540 Disambiguates between mode keywords and identifiers with the same name: 541 - MODE TYPE → keyword is identifier (e.g., "out INT") 542 - MODE NAME TYPE → keyword is mode (e.g., "OUT x INT") 543 544 Returns: 545 Mode token type if current token is a mode keyword, None otherwise. 546 """ 547 if not self._match_set(self.ARG_MODE_TOKENS, advance=False) or not self._next: 548 return None 549 550 mode_token = self._curr 551 552 # Check Pattern 1: MODE TYPE 553 # Try parsing next token as a built-in type (not UDT) 554 # If successful, the keyword is an identifier, not a mode 555 is_followed_by_builtin_type = self._try_parse( 556 lambda: self._advance() # type: ignore 557 or self._parse_types(check_func=False, allow_identifiers=False), 558 retreat=True, 559 ) 560 if is_followed_by_builtin_type: 561 return None # Pattern: "out INT" → out is parameter name 562 563 # Check Pattern 2: MODE NAME TYPE 564 # If next token is an identifier, check if there's a type after it 565 # The type can be built-in or user-defined (allow_identifiers=True) 566 if self._next.token_type not in self.ID_VAR_TOKENS: 567 return None 568 569 is_followed_by_any_type = self._try_parse( 570 lambda: self._advance(2) # type: ignore 571 or self._parse_types(check_func=False, allow_identifiers=True), 572 retreat=True, 573 ) 574 575 if is_followed_by_any_type: 576 return mode_token.token_type # Pattern: "OUT x INT" → OUT is mode 577 578 return None 579 580 def _create_mode_constraint(self, param_mode: TokenType) -> exp.InOutColumnConstraint: 581 """ 582 Create parameter mode constraint for function parameters. 583 584 Args: 585 param_mode: The parameter mode token (IN, OUT, INOUT, or VARIADIC). 586 587 Returns: 588 InOutColumnConstraint expression representing the parameter mode. 589 """ 590 return self.expression( 591 exp.InOutColumnConstraint, 592 input_=(param_mode in {TokenType.IN, TokenType.INOUT}), 593 output=(param_mode in {TokenType.OUT, TokenType.INOUT}), 594 variadic=(param_mode == TokenType.VARIADIC), 595 ) 596 597 def _parse_function_parameter(self) -> t.Optional[exp.Expression]: 598 param_mode = self._parse_parameter_mode() 599 600 if param_mode: 601 self._advance() 602 603 # Parse parameter name and type 604 param_name = self._parse_id_var() 605 column_def = self._parse_column_def(this=param_name, computed_column=False) 606 607 # Attach mode as constraint 608 if param_mode and column_def: 609 constraint = self._create_mode_constraint(param_mode) 610 if not column_def.args.get("constraints"): 611 column_def.set("constraints", []) 612 column_def.args["constraints"].insert(0, constraint) 613 614 return column_def 615 616 def _parse_query_parameter(self) -> t.Optional[exp.Expression]: 617 this = ( 618 self._parse_wrapped(self._parse_id_var) 619 if self._match(TokenType.L_PAREN, advance=False) 620 else None 621 ) 622 self._match_text_seq("S") 623 return self.expression(exp.Placeholder, this=this) 624 625 def _parse_date_part(self) -> exp.Expression: 626 part = self._parse_type() 627 self._match(TokenType.COMMA) 628 value = self._parse_bitwise() 629 630 if part and isinstance(part, (exp.Column, exp.Literal)): 631 part = exp.var(part.name) 632 633 return self.expression(exp.Extract, this=part, expression=value) 634 635 def _parse_unique_key(self) -> t.Optional[exp.Expression]: 636 return None 637 638 def _parse_jsonb_exists(self) -> exp.JSONBExists: 639 return self.expression( 640 exp.JSONBExists, 641 this=self._parse_bitwise(), 642 path=self._match(TokenType.COMMA) 643 and self.dialect.to_json_path(self._parse_bitwise()), 644 ) 645 646 def _parse_generated_as_identity( 647 self, 648 ) -> ( 649 exp.GeneratedAsIdentityColumnConstraint 650 | exp.ComputedColumnConstraint 651 | exp.GeneratedAsRowColumnConstraint 652 ): 653 this = super()._parse_generated_as_identity() 654 655 if self._match_text_seq("STORED"): 656 this = self.expression(exp.ComputedColumnConstraint, this=this.expression) 657 658 return this 659 660 def _parse_user_defined_type( 661 self, identifier: exp.Identifier 662 ) -> t.Optional[exp.Expression]: 663 udt_type: exp.Identifier | exp.Dot = identifier 664 665 while self._match(TokenType.DOT): 666 part = self._parse_id_var() 667 if part: 668 udt_type = exp.Dot(this=udt_type, expression=part) 669 670 return exp.DataType.build(udt_type, udt=True) 671 672 class Generator(generator.Generator): 673 SINGLE_STRING_INTERVAL = True 674 RENAME_TABLE_WITH_DB = False 675 LOCKING_READS_SUPPORTED = True 676 JOIN_HINTS = False 677 TABLE_HINTS = False 678 QUERY_HINTS = False 679 NVL2_SUPPORTED = False 680 PARAMETER_TOKEN = "$" 681 NAMED_PLACEHOLDER_TOKEN = "%" 682 TABLESAMPLE_SIZE_IS_ROWS = False 683 TABLESAMPLE_SEED_KEYWORD = "REPEATABLE" 684 SUPPORTS_SELECT_INTO = True 685 JSON_TYPE_REQUIRED_FOR_EXTRACTION = True 686 SUPPORTS_UNLOGGED_TABLES = True 687 LIKE_PROPERTY_INSIDE_SCHEMA = True 688 MULTI_ARG_DISTINCT = False 689 CAN_IMPLEMENT_ARRAY_ANY = True 690 SUPPORTS_WINDOW_EXCLUDE = True 691 COPY_HAS_INTO_KEYWORD = False 692 ARRAY_CONCAT_IS_VAR_LEN = False 693 SUPPORTS_MEDIAN = False 694 ARRAY_SIZE_DIM_REQUIRED = True 695 SUPPORTS_BETWEEN_FLAGS = True 696 INOUT_SEPARATOR = "" # PostgreSQL uses "INOUT" (no space) 697 698 SUPPORTED_JSON_PATH_PARTS = { 699 exp.JSONPathKey, 700 exp.JSONPathRoot, 701 exp.JSONPathSubscript, 702 } 703 704 def lateral_sql(self, expression: exp.Lateral) -> str: 705 sql = super().lateral_sql(expression) 706 707 if expression.args.get("cross_apply") is not None: 708 sql = f"{sql} ON TRUE" 709 710 return sql 711 712 TYPE_MAPPING = { 713 **generator.Generator.TYPE_MAPPING, 714 exp.DataType.Type.TINYINT: "SMALLINT", 715 exp.DataType.Type.FLOAT: "REAL", 716 exp.DataType.Type.DOUBLE: "DOUBLE PRECISION", 717 exp.DataType.Type.BINARY: "BYTEA", 718 exp.DataType.Type.VARBINARY: "BYTEA", 719 exp.DataType.Type.ROWVERSION: "BYTEA", 720 exp.DataType.Type.DATETIME: "TIMESTAMP", 721 exp.DataType.Type.TIMESTAMPNTZ: "TIMESTAMP", 722 exp.DataType.Type.BLOB: "BYTEA", 723 } 724 725 TRANSFORMS = { 726 **generator.Generator.TRANSFORMS, 727 exp.AnyValue: _versioned_anyvalue_sql, 728 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 729 exp.ArrayFilter: filter_array_using_unnest, 730 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 731 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND", swap_params=True), 732 exp.BitwiseAndAgg: rename_func("BIT_AND"), 733 exp.BitwiseOrAgg: rename_func("BIT_OR"), 734 exp.BitwiseXor: lambda self, e: self.binary(e, "#"), 735 exp.BitwiseXorAgg: rename_func("BIT_XOR"), 736 exp.ColumnDef: transforms.preprocess([_auto_increment_to_serial, _serial_to_generated]), 737 exp.CurrentDate: no_paren_current_date_sql, 738 exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP", 739 exp.CurrentUser: lambda *_: "CURRENT_USER", 740 exp.DateAdd: _date_add_sql("+"), 741 exp.DateDiff: _date_diff_sql, 742 exp.DateStrToDate: datestrtodate_sql, 743 exp.DateSub: _date_add_sql("-"), 744 exp.Explode: rename_func("UNNEST"), 745 exp.ExplodingGenerateSeries: rename_func("GENERATE_SERIES"), 746 exp.Getbit: getbit_sql, 747 exp.GroupConcat: lambda self, e: groupconcat_sql( 748 self, e, func_name="STRING_AGG", within_group=False 749 ), 750 exp.IntDiv: rename_func("DIV"), 751 exp.JSONArrayAgg: lambda self, e: self.func( 752 "JSON_AGG", 753 self.sql(e, "this"), 754 suffix=f"{self.sql(e, 'order')})", 755 ), 756 exp.JSONExtract: _json_extract_sql("JSON_EXTRACT_PATH", "->"), 757 exp.JSONExtractScalar: _json_extract_sql("JSON_EXTRACT_PATH_TEXT", "->>"), 758 exp.JSONBExtract: lambda self, e: self.binary(e, "#>"), 759 exp.JSONBExtractScalar: lambda self, e: self.binary(e, "#>>"), 760 exp.JSONBContains: lambda self, e: self.binary(e, "?"), 761 exp.ParseJSON: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.JSON)), 762 exp.JSONPathKey: json_path_key_only_name, 763 exp.JSONPathRoot: lambda *_: "", 764 exp.JSONPathSubscript: lambda self, e: self.json_path_part(e.this), 765 exp.LastDay: no_last_day_sql, 766 exp.LogicalOr: rename_func("BOOL_OR"), 767 exp.LogicalAnd: rename_func("BOOL_AND"), 768 exp.Max: max_or_greatest, 769 exp.MapFromEntries: no_map_from_entries_sql, 770 exp.Min: min_or_least, 771 exp.Merge: merge_without_target_sql, 772 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 773 exp.PercentileCont: transforms.preprocess( 774 [transforms.add_within_group_for_percentiles] 775 ), 776 exp.PercentileDisc: transforms.preprocess( 777 [transforms.add_within_group_for_percentiles] 778 ), 779 exp.Pivot: no_pivot_sql, 780 exp.Rand: rename_func("RANDOM"), 781 exp.RegexpLike: lambda self, e: self.binary(e, "~"), 782 exp.RegexpILike: lambda self, e: self.binary(e, "~*"), 783 exp.RegexpReplace: lambda self, e: self.func( 784 "REGEXP_REPLACE", 785 e.this, 786 e.expression, 787 e.args.get("replacement"), 788 e.args.get("position"), 789 e.args.get("occurrence"), 790 regexp_replace_global_modifier(e), 791 ), 792 exp.Round: _round_sql, 793 exp.Select: transforms.preprocess( 794 [ 795 transforms.eliminate_semi_and_anti_joins, 796 transforms.eliminate_qualify, 797 ] 798 ), 799 exp.SHA2: sha256_sql, 800 exp.SHA2Digest: sha2_digest_sql, 801 exp.StrPosition: lambda self, e: strposition_sql(self, e, func_name="POSITION"), 802 exp.StrToDate: lambda self, e: self.func("TO_DATE", e.this, self.format_time(e)), 803 exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)), 804 exp.StructExtract: struct_extract_sql, 805 exp.Substring: _substring_sql, 806 exp.TimeFromParts: rename_func("MAKE_TIME"), 807 exp.TimestampFromParts: rename_func("MAKE_TIMESTAMP"), 808 exp.TimestampTrunc: timestamptrunc_sql(zone=True), 809 exp.TimeStrToTime: timestrtotime_sql, 810 exp.TimeToStr: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)), 811 exp.ToChar: lambda self, e: self.function_fallback_sql(e) 812 if e.args.get("format") 813 else self.tochar_sql(e), 814 exp.Trim: trim_sql, 815 exp.TryCast: no_trycast_sql, 816 exp.TsOrDsAdd: _date_add_sql("+"), 817 exp.TsOrDsDiff: _date_diff_sql, 818 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this), 819 exp.Uuid: lambda *_: "GEN_RANDOM_UUID()", 820 exp.TimeToUnix: lambda self, e: self.func( 821 "DATE_PART", exp.Literal.string("epoch"), e.this 822 ), 823 exp.VariancePop: rename_func("VAR_POP"), 824 exp.Variance: rename_func("VAR_SAMP"), 825 exp.Xor: bool_xor_sql, 826 exp.Unicode: rename_func("ASCII"), 827 exp.UnixToTime: _unix_to_time_sql, 828 exp.Levenshtein: _levenshtein_sql, 829 exp.JSONObjectAgg: rename_func("JSON_OBJECT_AGG"), 830 exp.JSONBObjectAgg: rename_func("JSONB_OBJECT_AGG"), 831 exp.CountIf: count_if_to_sum, 832 } 833 834 TRANSFORMS.pop(exp.CommentColumnConstraint) 835 836 PROPERTIES_LOCATION = { 837 **generator.Generator.PROPERTIES_LOCATION, 838 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 839 exp.TransientProperty: exp.Properties.Location.UNSUPPORTED, 840 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 841 } 842 843 def schemacommentproperty_sql(self, expression: exp.SchemaCommentProperty) -> str: 844 self.unsupported("Table comments are not supported in the CREATE statement") 845 return "" 846 847 def commentcolumnconstraint_sql(self, expression: exp.CommentColumnConstraint) -> str: 848 self.unsupported("Column comments are not supported in the CREATE statement") 849 return "" 850 851 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 852 # PostgreSQL places parameter modes BEFORE parameter name 853 param_constraint = expression.find(exp.InOutColumnConstraint) 854 855 if param_constraint: 856 mode_sql = self.sql(param_constraint) 857 param_constraint.pop() # Remove to prevent double-rendering 858 base_sql = super().columndef_sql(expression, sep) 859 return f"{mode_sql} {base_sql}" 860 861 return super().columndef_sql(expression, sep) 862 863 def unnest_sql(self, expression: exp.Unnest) -> str: 864 if len(expression.expressions) == 1: 865 arg = expression.expressions[0] 866 if isinstance(arg, exp.GenerateDateArray): 867 generate_series: exp.Expression = exp.GenerateSeries(**arg.args) 868 if isinstance(expression.parent, (exp.From, exp.Join)): 869 generate_series = ( 870 exp.select("value::date") 871 .from_(exp.Table(this=generate_series).as_("_t", table=["value"])) 872 .subquery(expression.args.get("alias") or "_unnested_generate_series") 873 ) 874 return self.sql(generate_series) 875 876 from sqlglot.optimizer.annotate_types import annotate_types 877 878 this = annotate_types(arg, dialect=self.dialect) 879 if this.is_type("array<json>"): 880 while isinstance(this, exp.Cast): 881 this = this.this 882 883 arg_as_json = self.sql(exp.cast(this, exp.DataType.Type.JSON)) 884 alias = self.sql(expression, "alias") 885 alias = f" AS {alias}" if alias else "" 886 887 if expression.args.get("offset"): 888 self.unsupported("Unsupported JSON_ARRAY_ELEMENTS with offset") 889 890 return f"JSON_ARRAY_ELEMENTS({arg_as_json}){alias}" 891 892 return super().unnest_sql(expression) 893 894 def bracket_sql(self, expression: exp.Bracket) -> str: 895 """Forms like ARRAY[1, 2, 3][3] aren't allowed; we need to wrap the ARRAY.""" 896 if isinstance(expression.this, exp.Array): 897 expression.set("this", exp.paren(expression.this, copy=False)) 898 899 return super().bracket_sql(expression) 900 901 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 902 this = self.sql(expression, "this") 903 expressions = [f"{self.sql(e)} @@ {this}" for e in expression.expressions] 904 sql = " OR ".join(expressions) 905 return f"({sql})" if len(expressions) > 1 else sql 906 907 def alterset_sql(self, expression: exp.AlterSet) -> str: 908 exprs = self.expressions(expression, flat=True) 909 exprs = f"({exprs})" if exprs else "" 910 911 access_method = self.sql(expression, "access_method") 912 access_method = f"ACCESS METHOD {access_method}" if access_method else "" 913 tablespace = self.sql(expression, "tablespace") 914 tablespace = f"TABLESPACE {tablespace}" if tablespace else "" 915 option = self.sql(expression, "option") 916 917 return f"SET {exprs}{access_method}{tablespace}{option}" 918 919 def datatype_sql(self, expression: exp.DataType) -> str: 920 if expression.is_type(exp.DataType.Type.ARRAY): 921 if expression.expressions: 922 values = self.expressions(expression, key="values", flat=True) 923 return f"{self.expressions(expression, flat=True)}[{values}]" 924 return "ARRAY" 925 926 if ( 927 expression.is_type(exp.DataType.Type.DOUBLE, exp.DataType.Type.FLOAT) 928 and expression.expressions 929 ): 930 # Postgres doesn't support precision for REAL and DOUBLE PRECISION types 931 return f"FLOAT({self.expressions(expression, flat=True)})" 932 933 return super().datatype_sql(expression) 934 935 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 936 this = expression.this 937 938 # Postgres casts DIV() to decimal for transpilation but when roundtripping it's superfluous 939 if isinstance(this, exp.IntDiv) and expression.to == exp.DataType.build("decimal"): 940 return self.sql(this) 941 942 return super().cast_sql(expression, safe_prefix=safe_prefix) 943 944 def array_sql(self, expression: exp.Array) -> str: 945 exprs = expression.expressions 946 func_name = self.normalize_func("ARRAY") 947 948 if isinstance(seq_get(exprs, 0), exp.Select): 949 return f"{func_name}({self.sql(exprs[0])})" 950 951 return f"{func_name}{inline_array_sql(self, expression)}" 952 953 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 954 return f"GENERATED ALWAYS AS ({self.sql(expression, 'this')}) STORED" 955 956 def isascii_sql(self, expression: exp.IsAscii) -> str: 957 return f"({self.sql(expression.this)} ~ '^[[:ascii:]]*$')" 958 959 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 960 # https://www.postgresql.org/docs/current/functions-window.html 961 self.unsupported("PostgreSQL does not support IGNORE NULLS.") 962 return self.sql(expression.this) 963 964 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 965 # https://www.postgresql.org/docs/current/functions-window.html 966 self.unsupported("PostgreSQL does not support RESPECT NULLS.") 967 return self.sql(expression.this) 968 969 @unsupported_args("this") 970 def currentschema_sql(self, expression: exp.CurrentSchema) -> str: 971 return "CURRENT_SCHEMA" 972 973 def interval_sql(self, expression: exp.Interval) -> str: 974 unit = expression.text("unit").lower() 975 976 this = expression.this 977 if unit.startswith("quarter") and isinstance(this, exp.Literal): 978 this.replace(exp.Literal.string(int(this.to_py()) * 3)) 979 expression.args["unit"].replace(exp.var("MONTH")) 980 981 return super().interval_sql(expression) 982 983 def placeholder_sql(self, expression: exp.Placeholder) -> str: 984 if expression.args.get("jdbc"): 985 return "?" 986 987 this = f"({expression.name})" if expression.this else "" 988 return f"{self.NAMED_PLACEHOLDER_TOKEN}{this}s" 989 990 def arraycontains_sql(self, expression: exp.ArrayContains) -> str: 991 # Convert DuckDB's LIST_CONTAINS(array, value) to PostgreSQL 992 # DuckDB behavior: 993 # - LIST_CONTAINS([1,2,3], 2) -> true 994 # - LIST_CONTAINS([1,2,3], 4) -> false 995 # - LIST_CONTAINS([1,2,NULL], 4) -> false (not NULL) 996 # - LIST_CONTAINS([1,2,3], NULL) -> NULL 997 # 998 # PostgreSQL equivalent: CASE WHEN value IS NULL THEN NULL 999 # ELSE COALESCE(value = ANY(array), FALSE) END 1000 value = expression.expression 1001 array = expression.this 1002 1003 coalesce_expr = exp.Coalesce( 1004 this=value.eq(exp.Any(this=exp.paren(expression=array, copy=False))), 1005 expressions=[exp.false()], 1006 ) 1007 1008 case_expr = ( 1009 exp.Case() 1010 .when(exp.Is(this=value, expression=exp.null()), exp.null(), copy=False) 1011 .else_(coalesce_expr, copy=False) 1012 ) 1013 1014 return self.sql(case_expr)
298class Postgres(Dialect): 299 INDEX_OFFSET = 1 300 TYPED_DIVISION = True 301 CONCAT_COALESCE = True 302 NULL_ORDERING = "nulls_are_large" 303 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 304 TABLESAMPLE_SIZE_IS_PERCENT = True 305 TABLES_REFERENCEABLE_AS_COLUMNS = True 306 307 DEFAULT_FUNCTIONS_COLUMN_NAMES = { 308 exp.ExplodingGenerateSeries: "generate_series", 309 } 310 311 TIME_MAPPING = { 312 "d": "%u", # 1-based day of week 313 "D": "%u", # 1-based day of week 314 "dd": "%d", # day of month 315 "DD": "%d", # day of month 316 "ddd": "%j", # zero padded day of year 317 "DDD": "%j", # zero padded day of year 318 "FMDD": "%-d", # - is no leading zero for Python; same for FM in postgres 319 "FMDDD": "%-j", # day of year 320 "FMHH12": "%-I", # 9 321 "FMHH24": "%-H", # 9 322 "FMMI": "%-M", # Minute 323 "FMMM": "%-m", # 1 324 "FMSS": "%-S", # Second 325 "HH12": "%I", # 09 326 "HH24": "%H", # 09 327 "mi": "%M", # zero padded minute 328 "MI": "%M", # zero padded minute 329 "mm": "%m", # 01 330 "MM": "%m", # 01 331 "OF": "%z", # utc offset 332 "ss": "%S", # zero padded second 333 "SS": "%S", # zero padded second 334 "TMDay": "%A", # TM is locale dependent 335 "TMDy": "%a", 336 "TMMon": "%b", # Sep 337 "TMMonth": "%B", # September 338 "TZ": "%Z", # uppercase timezone name 339 "US": "%f", # zero padded microsecond 340 "ww": "%U", # 1-based week of year 341 "WW": "%U", # 1-based week of year 342 "yy": "%y", # 15 343 "YY": "%y", # 15 344 "yyyy": "%Y", # 2015 345 "YYYY": "%Y", # 2015 346 } 347 348 class Tokenizer(tokens.Tokenizer): 349 BIT_STRINGS = [("b'", "'"), ("B'", "'")] 350 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 351 BYTE_STRINGS = [("e'", "'"), ("E'", "'")] 352 BYTE_STRING_ESCAPES = ["'", "\\"] 353 HEREDOC_STRINGS = ["$"] 354 355 HEREDOC_TAG_IS_IDENTIFIER = True 356 HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER 357 358 KEYWORDS = { 359 **tokens.Tokenizer.KEYWORDS, 360 "~": TokenType.RLIKE, 361 "@@": TokenType.DAT, 362 "@>": TokenType.AT_GT, 363 "<@": TokenType.LT_AT, 364 "?&": TokenType.QMARK_AMP, 365 "?|": TokenType.QMARK_PIPE, 366 "#-": TokenType.HASH_DASH, 367 "|/": TokenType.PIPE_SLASH, 368 "||/": TokenType.DPIPE_SLASH, 369 "BEGIN": TokenType.BEGIN, 370 "BIGSERIAL": TokenType.BIGSERIAL, 371 "CONSTRAINT TRIGGER": TokenType.COMMAND, 372 "CSTRING": TokenType.PSEUDO_TYPE, 373 "DECLARE": TokenType.COMMAND, 374 "DO": TokenType.COMMAND, 375 "EXEC": TokenType.COMMAND, 376 "HSTORE": TokenType.HSTORE, 377 "INT8": TokenType.BIGINT, 378 "MONEY": TokenType.MONEY, 379 "NAME": TokenType.NAME, 380 "OID": TokenType.OBJECT_IDENTIFIER, 381 "ONLY": TokenType.ONLY, 382 "POINT": TokenType.POINT, 383 "REFRESH": TokenType.COMMAND, 384 "REINDEX": TokenType.COMMAND, 385 "RESET": TokenType.COMMAND, 386 "SERIAL": TokenType.SERIAL, 387 "SMALLSERIAL": TokenType.SMALLSERIAL, 388 "TEMP": TokenType.TEMPORARY, 389 "REGCLASS": TokenType.OBJECT_IDENTIFIER, 390 "REGCOLLATION": TokenType.OBJECT_IDENTIFIER, 391 "REGCONFIG": TokenType.OBJECT_IDENTIFIER, 392 "REGDICTIONARY": TokenType.OBJECT_IDENTIFIER, 393 "REGNAMESPACE": TokenType.OBJECT_IDENTIFIER, 394 "REGOPER": TokenType.OBJECT_IDENTIFIER, 395 "REGOPERATOR": TokenType.OBJECT_IDENTIFIER, 396 "REGPROC": TokenType.OBJECT_IDENTIFIER, 397 "REGPROCEDURE": TokenType.OBJECT_IDENTIFIER, 398 "REGROLE": TokenType.OBJECT_IDENTIFIER, 399 "REGTYPE": TokenType.OBJECT_IDENTIFIER, 400 "FLOAT": TokenType.DOUBLE, 401 "XML": TokenType.XML, 402 "VARIADIC": TokenType.VARIADIC, 403 "INOUT": TokenType.INOUT, 404 } 405 KEYWORDS.pop("/*+") 406 KEYWORDS.pop("DIV") 407 408 SINGLE_TOKENS = { 409 **tokens.Tokenizer.SINGLE_TOKENS, 410 "$": TokenType.HEREDOC_STRING, 411 } 412 413 VAR_SINGLE_TOKENS = {"$"} 414 415 class Parser(parser.Parser): 416 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = True 417 418 PROPERTY_PARSERS = { 419 **parser.Parser.PROPERTY_PARSERS, 420 "SET": lambda self: self.expression(exp.SetConfigProperty, this=self._parse_set()), 421 } 422 PROPERTY_PARSERS.pop("INPUT") 423 424 PLACEHOLDER_PARSERS = { 425 **parser.Parser.PLACEHOLDER_PARSERS, 426 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder, jdbc=True), 427 TokenType.MOD: lambda self: self._parse_query_parameter(), 428 } 429 430 FUNCTIONS = { 431 **parser.Parser.FUNCTIONS, 432 "ARRAY_PREPEND": lambda args: exp.ArrayPrepend( 433 this=seq_get(args, 1), expression=seq_get(args, 0) 434 ), 435 "BIT_AND": exp.BitwiseAndAgg.from_arg_list, 436 "BIT_OR": exp.BitwiseOrAgg.from_arg_list, 437 "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, 438 "DATE_TRUNC": build_timestamp_trunc, 439 "DIV": lambda args: exp.cast( 440 binary_from_function(exp.IntDiv)(args), exp.DataType.Type.DECIMAL 441 ), 442 "GENERATE_SERIES": _build_generate_series, 443 "GET_BIT": lambda args: exp.Getbit( 444 this=seq_get(args, 0), expression=seq_get(args, 1), zero_is_msb=True 445 ), 446 "JSON_EXTRACT_PATH": build_json_extract_path(exp.JSONExtract), 447 "JSON_EXTRACT_PATH_TEXT": build_json_extract_path(exp.JSONExtractScalar), 448 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), encoding=seq_get(args, 1)), 449 "MAKE_TIME": exp.TimeFromParts.from_arg_list, 450 "MAKE_TIMESTAMP": exp.TimestampFromParts.from_arg_list, 451 "NOW": exp.CurrentTimestamp.from_arg_list, 452 "REGEXP_REPLACE": _build_regexp_replace, 453 "TO_CHAR": build_formatted_time(exp.TimeToStr, "postgres"), 454 "TO_DATE": build_formatted_time(exp.StrToDate, "postgres"), 455 "TO_TIMESTAMP": _build_to_timestamp, 456 "UNNEST": exp.Explode.from_arg_list, 457 "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)), 458 "SHA384": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(384)), 459 "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)), 460 "LEVENSHTEIN_LESS_EQUAL": _build_levenshtein_less_equal, 461 "JSON_OBJECT_AGG": lambda args: exp.JSONObjectAgg(expressions=args), 462 "JSONB_OBJECT_AGG": exp.JSONBObjectAgg.from_arg_list, 463 "WIDTH_BUCKET": lambda args: exp.WidthBucket( 464 this=seq_get(args, 0), threshold=seq_get(args, 1) 465 ) 466 if len(args) == 2 467 else exp.WidthBucket.from_arg_list(args), 468 } 469 470 NO_PAREN_FUNCTION_PARSERS = { 471 **parser.Parser.NO_PAREN_FUNCTION_PARSERS, 472 "VARIADIC": lambda self: self.expression(exp.Variadic, this=self._parse_bitwise()), 473 } 474 475 NO_PAREN_FUNCTIONS = { 476 **parser.Parser.NO_PAREN_FUNCTIONS, 477 TokenType.CURRENT_SCHEMA: exp.CurrentSchema, 478 } 479 480 FUNCTION_PARSERS = { 481 **parser.Parser.FUNCTION_PARSERS, 482 "DATE_PART": lambda self: self._parse_date_part(), 483 "JSON_AGG": lambda self: self.expression( 484 exp.JSONArrayAgg, 485 this=self._parse_lambda(), 486 order=self._parse_order(), 487 ), 488 "JSONB_EXISTS": lambda self: self._parse_jsonb_exists(), 489 } 490 491 BITWISE = { 492 **parser.Parser.BITWISE, 493 TokenType.HASH: exp.BitwiseXor, 494 } 495 496 EXPONENT = { 497 TokenType.CARET: exp.Pow, 498 } 499 500 RANGE_PARSERS = { 501 **parser.Parser.RANGE_PARSERS, 502 TokenType.DAMP: binary_range_parser(exp.ArrayOverlaps), 503 TokenType.DAT: lambda self, this: self.expression( 504 exp.MatchAgainst, this=self._parse_bitwise(), expressions=[this] 505 ), 506 } 507 508 STATEMENT_PARSERS = { 509 **parser.Parser.STATEMENT_PARSERS, 510 TokenType.END: lambda self: self._parse_commit_or_rollback(), 511 } 512 513 UNARY_PARSERS = { 514 **parser.Parser.UNARY_PARSERS, 515 # The `~` token is remapped from TILDE to RLIKE in Postgres due to the binary REGEXP LIKE operator 516 TokenType.RLIKE: lambda self: self.expression(exp.BitwiseNot, this=self._parse_unary()), 517 } 518 519 JSON_ARROWS_REQUIRE_JSON_TYPE = True 520 521 COLUMN_OPERATORS = { 522 **parser.Parser.COLUMN_OPERATORS, 523 TokenType.ARROW: lambda self, this, path: self.validate_expression( 524 build_json_extract_path( 525 exp.JSONExtract, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE 526 )([this, path]) 527 ), 528 TokenType.DARROW: lambda self, this, path: self.validate_expression( 529 build_json_extract_path( 530 exp.JSONExtractScalar, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE 531 )([this, path]) 532 ), 533 } 534 535 ARG_MODE_TOKENS = {TokenType.IN, TokenType.OUT, TokenType.INOUT, TokenType.VARIADIC} 536 537 def _parse_parameter_mode(self) -> t.Optional[TokenType]: 538 """ 539 Parse PostgreSQL function parameter mode (IN, OUT, INOUT, VARIADIC). 540 541 Disambiguates between mode keywords and identifiers with the same name: 542 - MODE TYPE → keyword is identifier (e.g., "out INT") 543 - MODE NAME TYPE → keyword is mode (e.g., "OUT x INT") 544 545 Returns: 546 Mode token type if current token is a mode keyword, None otherwise. 547 """ 548 if not self._match_set(self.ARG_MODE_TOKENS, advance=False) or not self._next: 549 return None 550 551 mode_token = self._curr 552 553 # Check Pattern 1: MODE TYPE 554 # Try parsing next token as a built-in type (not UDT) 555 # If successful, the keyword is an identifier, not a mode 556 is_followed_by_builtin_type = self._try_parse( 557 lambda: self._advance() # type: ignore 558 or self._parse_types(check_func=False, allow_identifiers=False), 559 retreat=True, 560 ) 561 if is_followed_by_builtin_type: 562 return None # Pattern: "out INT" → out is parameter name 563 564 # Check Pattern 2: MODE NAME TYPE 565 # If next token is an identifier, check if there's a type after it 566 # The type can be built-in or user-defined (allow_identifiers=True) 567 if self._next.token_type not in self.ID_VAR_TOKENS: 568 return None 569 570 is_followed_by_any_type = self._try_parse( 571 lambda: self._advance(2) # type: ignore 572 or self._parse_types(check_func=False, allow_identifiers=True), 573 retreat=True, 574 ) 575 576 if is_followed_by_any_type: 577 return mode_token.token_type # Pattern: "OUT x INT" → OUT is mode 578 579 return None 580 581 def _create_mode_constraint(self, param_mode: TokenType) -> exp.InOutColumnConstraint: 582 """ 583 Create parameter mode constraint for function parameters. 584 585 Args: 586 param_mode: The parameter mode token (IN, OUT, INOUT, or VARIADIC). 587 588 Returns: 589 InOutColumnConstraint expression representing the parameter mode. 590 """ 591 return self.expression( 592 exp.InOutColumnConstraint, 593 input_=(param_mode in {TokenType.IN, TokenType.INOUT}), 594 output=(param_mode in {TokenType.OUT, TokenType.INOUT}), 595 variadic=(param_mode == TokenType.VARIADIC), 596 ) 597 598 def _parse_function_parameter(self) -> t.Optional[exp.Expression]: 599 param_mode = self._parse_parameter_mode() 600 601 if param_mode: 602 self._advance() 603 604 # Parse parameter name and type 605 param_name = self._parse_id_var() 606 column_def = self._parse_column_def(this=param_name, computed_column=False) 607 608 # Attach mode as constraint 609 if param_mode and column_def: 610 constraint = self._create_mode_constraint(param_mode) 611 if not column_def.args.get("constraints"): 612 column_def.set("constraints", []) 613 column_def.args["constraints"].insert(0, constraint) 614 615 return column_def 616 617 def _parse_query_parameter(self) -> t.Optional[exp.Expression]: 618 this = ( 619 self._parse_wrapped(self._parse_id_var) 620 if self._match(TokenType.L_PAREN, advance=False) 621 else None 622 ) 623 self._match_text_seq("S") 624 return self.expression(exp.Placeholder, this=this) 625 626 def _parse_date_part(self) -> exp.Expression: 627 part = self._parse_type() 628 self._match(TokenType.COMMA) 629 value = self._parse_bitwise() 630 631 if part and isinstance(part, (exp.Column, exp.Literal)): 632 part = exp.var(part.name) 633 634 return self.expression(exp.Extract, this=part, expression=value) 635 636 def _parse_unique_key(self) -> t.Optional[exp.Expression]: 637 return None 638 639 def _parse_jsonb_exists(self) -> exp.JSONBExists: 640 return self.expression( 641 exp.JSONBExists, 642 this=self._parse_bitwise(), 643 path=self._match(TokenType.COMMA) 644 and self.dialect.to_json_path(self._parse_bitwise()), 645 ) 646 647 def _parse_generated_as_identity( 648 self, 649 ) -> ( 650 exp.GeneratedAsIdentityColumnConstraint 651 | exp.ComputedColumnConstraint 652 | exp.GeneratedAsRowColumnConstraint 653 ): 654 this = super()._parse_generated_as_identity() 655 656 if self._match_text_seq("STORED"): 657 this = self.expression(exp.ComputedColumnConstraint, this=this.expression) 658 659 return this 660 661 def _parse_user_defined_type( 662 self, identifier: exp.Identifier 663 ) -> t.Optional[exp.Expression]: 664 udt_type: exp.Identifier | exp.Dot = identifier 665 666 while self._match(TokenType.DOT): 667 part = self._parse_id_var() 668 if part: 669 udt_type = exp.Dot(this=udt_type, expression=part) 670 671 return exp.DataType.build(udt_type, udt=True) 672 673 class Generator(generator.Generator): 674 SINGLE_STRING_INTERVAL = True 675 RENAME_TABLE_WITH_DB = False 676 LOCKING_READS_SUPPORTED = True 677 JOIN_HINTS = False 678 TABLE_HINTS = False 679 QUERY_HINTS = False 680 NVL2_SUPPORTED = False 681 PARAMETER_TOKEN = "$" 682 NAMED_PLACEHOLDER_TOKEN = "%" 683 TABLESAMPLE_SIZE_IS_ROWS = False 684 TABLESAMPLE_SEED_KEYWORD = "REPEATABLE" 685 SUPPORTS_SELECT_INTO = True 686 JSON_TYPE_REQUIRED_FOR_EXTRACTION = True 687 SUPPORTS_UNLOGGED_TABLES = True 688 LIKE_PROPERTY_INSIDE_SCHEMA = True 689 MULTI_ARG_DISTINCT = False 690 CAN_IMPLEMENT_ARRAY_ANY = True 691 SUPPORTS_WINDOW_EXCLUDE = True 692 COPY_HAS_INTO_KEYWORD = False 693 ARRAY_CONCAT_IS_VAR_LEN = False 694 SUPPORTS_MEDIAN = False 695 ARRAY_SIZE_DIM_REQUIRED = True 696 SUPPORTS_BETWEEN_FLAGS = True 697 INOUT_SEPARATOR = "" # PostgreSQL uses "INOUT" (no space) 698 699 SUPPORTED_JSON_PATH_PARTS = { 700 exp.JSONPathKey, 701 exp.JSONPathRoot, 702 exp.JSONPathSubscript, 703 } 704 705 def lateral_sql(self, expression: exp.Lateral) -> str: 706 sql = super().lateral_sql(expression) 707 708 if expression.args.get("cross_apply") is not None: 709 sql = f"{sql} ON TRUE" 710 711 return sql 712 713 TYPE_MAPPING = { 714 **generator.Generator.TYPE_MAPPING, 715 exp.DataType.Type.TINYINT: "SMALLINT", 716 exp.DataType.Type.FLOAT: "REAL", 717 exp.DataType.Type.DOUBLE: "DOUBLE PRECISION", 718 exp.DataType.Type.BINARY: "BYTEA", 719 exp.DataType.Type.VARBINARY: "BYTEA", 720 exp.DataType.Type.ROWVERSION: "BYTEA", 721 exp.DataType.Type.DATETIME: "TIMESTAMP", 722 exp.DataType.Type.TIMESTAMPNTZ: "TIMESTAMP", 723 exp.DataType.Type.BLOB: "BYTEA", 724 } 725 726 TRANSFORMS = { 727 **generator.Generator.TRANSFORMS, 728 exp.AnyValue: _versioned_anyvalue_sql, 729 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 730 exp.ArrayFilter: filter_array_using_unnest, 731 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 732 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND", swap_params=True), 733 exp.BitwiseAndAgg: rename_func("BIT_AND"), 734 exp.BitwiseOrAgg: rename_func("BIT_OR"), 735 exp.BitwiseXor: lambda self, e: self.binary(e, "#"), 736 exp.BitwiseXorAgg: rename_func("BIT_XOR"), 737 exp.ColumnDef: transforms.preprocess([_auto_increment_to_serial, _serial_to_generated]), 738 exp.CurrentDate: no_paren_current_date_sql, 739 exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP", 740 exp.CurrentUser: lambda *_: "CURRENT_USER", 741 exp.DateAdd: _date_add_sql("+"), 742 exp.DateDiff: _date_diff_sql, 743 exp.DateStrToDate: datestrtodate_sql, 744 exp.DateSub: _date_add_sql("-"), 745 exp.Explode: rename_func("UNNEST"), 746 exp.ExplodingGenerateSeries: rename_func("GENERATE_SERIES"), 747 exp.Getbit: getbit_sql, 748 exp.GroupConcat: lambda self, e: groupconcat_sql( 749 self, e, func_name="STRING_AGG", within_group=False 750 ), 751 exp.IntDiv: rename_func("DIV"), 752 exp.JSONArrayAgg: lambda self, e: self.func( 753 "JSON_AGG", 754 self.sql(e, "this"), 755 suffix=f"{self.sql(e, 'order')})", 756 ), 757 exp.JSONExtract: _json_extract_sql("JSON_EXTRACT_PATH", "->"), 758 exp.JSONExtractScalar: _json_extract_sql("JSON_EXTRACT_PATH_TEXT", "->>"), 759 exp.JSONBExtract: lambda self, e: self.binary(e, "#>"), 760 exp.JSONBExtractScalar: lambda self, e: self.binary(e, "#>>"), 761 exp.JSONBContains: lambda self, e: self.binary(e, "?"), 762 exp.ParseJSON: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.JSON)), 763 exp.JSONPathKey: json_path_key_only_name, 764 exp.JSONPathRoot: lambda *_: "", 765 exp.JSONPathSubscript: lambda self, e: self.json_path_part(e.this), 766 exp.LastDay: no_last_day_sql, 767 exp.LogicalOr: rename_func("BOOL_OR"), 768 exp.LogicalAnd: rename_func("BOOL_AND"), 769 exp.Max: max_or_greatest, 770 exp.MapFromEntries: no_map_from_entries_sql, 771 exp.Min: min_or_least, 772 exp.Merge: merge_without_target_sql, 773 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 774 exp.PercentileCont: transforms.preprocess( 775 [transforms.add_within_group_for_percentiles] 776 ), 777 exp.PercentileDisc: transforms.preprocess( 778 [transforms.add_within_group_for_percentiles] 779 ), 780 exp.Pivot: no_pivot_sql, 781 exp.Rand: rename_func("RANDOM"), 782 exp.RegexpLike: lambda self, e: self.binary(e, "~"), 783 exp.RegexpILike: lambda self, e: self.binary(e, "~*"), 784 exp.RegexpReplace: lambda self, e: self.func( 785 "REGEXP_REPLACE", 786 e.this, 787 e.expression, 788 e.args.get("replacement"), 789 e.args.get("position"), 790 e.args.get("occurrence"), 791 regexp_replace_global_modifier(e), 792 ), 793 exp.Round: _round_sql, 794 exp.Select: transforms.preprocess( 795 [ 796 transforms.eliminate_semi_and_anti_joins, 797 transforms.eliminate_qualify, 798 ] 799 ), 800 exp.SHA2: sha256_sql, 801 exp.SHA2Digest: sha2_digest_sql, 802 exp.StrPosition: lambda self, e: strposition_sql(self, e, func_name="POSITION"), 803 exp.StrToDate: lambda self, e: self.func("TO_DATE", e.this, self.format_time(e)), 804 exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)), 805 exp.StructExtract: struct_extract_sql, 806 exp.Substring: _substring_sql, 807 exp.TimeFromParts: rename_func("MAKE_TIME"), 808 exp.TimestampFromParts: rename_func("MAKE_TIMESTAMP"), 809 exp.TimestampTrunc: timestamptrunc_sql(zone=True), 810 exp.TimeStrToTime: timestrtotime_sql, 811 exp.TimeToStr: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)), 812 exp.ToChar: lambda self, e: self.function_fallback_sql(e) 813 if e.args.get("format") 814 else self.tochar_sql(e), 815 exp.Trim: trim_sql, 816 exp.TryCast: no_trycast_sql, 817 exp.TsOrDsAdd: _date_add_sql("+"), 818 exp.TsOrDsDiff: _date_diff_sql, 819 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this), 820 exp.Uuid: lambda *_: "GEN_RANDOM_UUID()", 821 exp.TimeToUnix: lambda self, e: self.func( 822 "DATE_PART", exp.Literal.string("epoch"), e.this 823 ), 824 exp.VariancePop: rename_func("VAR_POP"), 825 exp.Variance: rename_func("VAR_SAMP"), 826 exp.Xor: bool_xor_sql, 827 exp.Unicode: rename_func("ASCII"), 828 exp.UnixToTime: _unix_to_time_sql, 829 exp.Levenshtein: _levenshtein_sql, 830 exp.JSONObjectAgg: rename_func("JSON_OBJECT_AGG"), 831 exp.JSONBObjectAgg: rename_func("JSONB_OBJECT_AGG"), 832 exp.CountIf: count_if_to_sum, 833 } 834 835 TRANSFORMS.pop(exp.CommentColumnConstraint) 836 837 PROPERTIES_LOCATION = { 838 **generator.Generator.PROPERTIES_LOCATION, 839 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 840 exp.TransientProperty: exp.Properties.Location.UNSUPPORTED, 841 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 842 } 843 844 def schemacommentproperty_sql(self, expression: exp.SchemaCommentProperty) -> str: 845 self.unsupported("Table comments are not supported in the CREATE statement") 846 return "" 847 848 def commentcolumnconstraint_sql(self, expression: exp.CommentColumnConstraint) -> str: 849 self.unsupported("Column comments are not supported in the CREATE statement") 850 return "" 851 852 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 853 # PostgreSQL places parameter modes BEFORE parameter name 854 param_constraint = expression.find(exp.InOutColumnConstraint) 855 856 if param_constraint: 857 mode_sql = self.sql(param_constraint) 858 param_constraint.pop() # Remove to prevent double-rendering 859 base_sql = super().columndef_sql(expression, sep) 860 return f"{mode_sql} {base_sql}" 861 862 return super().columndef_sql(expression, sep) 863 864 def unnest_sql(self, expression: exp.Unnest) -> str: 865 if len(expression.expressions) == 1: 866 arg = expression.expressions[0] 867 if isinstance(arg, exp.GenerateDateArray): 868 generate_series: exp.Expression = exp.GenerateSeries(**arg.args) 869 if isinstance(expression.parent, (exp.From, exp.Join)): 870 generate_series = ( 871 exp.select("value::date") 872 .from_(exp.Table(this=generate_series).as_("_t", table=["value"])) 873 .subquery(expression.args.get("alias") or "_unnested_generate_series") 874 ) 875 return self.sql(generate_series) 876 877 from sqlglot.optimizer.annotate_types import annotate_types 878 879 this = annotate_types(arg, dialect=self.dialect) 880 if this.is_type("array<json>"): 881 while isinstance(this, exp.Cast): 882 this = this.this 883 884 arg_as_json = self.sql(exp.cast(this, exp.DataType.Type.JSON)) 885 alias = self.sql(expression, "alias") 886 alias = f" AS {alias}" if alias else "" 887 888 if expression.args.get("offset"): 889 self.unsupported("Unsupported JSON_ARRAY_ELEMENTS with offset") 890 891 return f"JSON_ARRAY_ELEMENTS({arg_as_json}){alias}" 892 893 return super().unnest_sql(expression) 894 895 def bracket_sql(self, expression: exp.Bracket) -> str: 896 """Forms like ARRAY[1, 2, 3][3] aren't allowed; we need to wrap the ARRAY.""" 897 if isinstance(expression.this, exp.Array): 898 expression.set("this", exp.paren(expression.this, copy=False)) 899 900 return super().bracket_sql(expression) 901 902 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 903 this = self.sql(expression, "this") 904 expressions = [f"{self.sql(e)} @@ {this}" for e in expression.expressions] 905 sql = " OR ".join(expressions) 906 return f"({sql})" if len(expressions) > 1 else sql 907 908 def alterset_sql(self, expression: exp.AlterSet) -> str: 909 exprs = self.expressions(expression, flat=True) 910 exprs = f"({exprs})" if exprs else "" 911 912 access_method = self.sql(expression, "access_method") 913 access_method = f"ACCESS METHOD {access_method}" if access_method else "" 914 tablespace = self.sql(expression, "tablespace") 915 tablespace = f"TABLESPACE {tablespace}" if tablespace else "" 916 option = self.sql(expression, "option") 917 918 return f"SET {exprs}{access_method}{tablespace}{option}" 919 920 def datatype_sql(self, expression: exp.DataType) -> str: 921 if expression.is_type(exp.DataType.Type.ARRAY): 922 if expression.expressions: 923 values = self.expressions(expression, key="values", flat=True) 924 return f"{self.expressions(expression, flat=True)}[{values}]" 925 return "ARRAY" 926 927 if ( 928 expression.is_type(exp.DataType.Type.DOUBLE, exp.DataType.Type.FLOAT) 929 and expression.expressions 930 ): 931 # Postgres doesn't support precision for REAL and DOUBLE PRECISION types 932 return f"FLOAT({self.expressions(expression, flat=True)})" 933 934 return super().datatype_sql(expression) 935 936 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 937 this = expression.this 938 939 # Postgres casts DIV() to decimal for transpilation but when roundtripping it's superfluous 940 if isinstance(this, exp.IntDiv) and expression.to == exp.DataType.build("decimal"): 941 return self.sql(this) 942 943 return super().cast_sql(expression, safe_prefix=safe_prefix) 944 945 def array_sql(self, expression: exp.Array) -> str: 946 exprs = expression.expressions 947 func_name = self.normalize_func("ARRAY") 948 949 if isinstance(seq_get(exprs, 0), exp.Select): 950 return f"{func_name}({self.sql(exprs[0])})" 951 952 return f"{func_name}{inline_array_sql(self, expression)}" 953 954 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 955 return f"GENERATED ALWAYS AS ({self.sql(expression, 'this')}) STORED" 956 957 def isascii_sql(self, expression: exp.IsAscii) -> str: 958 return f"({self.sql(expression.this)} ~ '^[[:ascii:]]*$')" 959 960 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 961 # https://www.postgresql.org/docs/current/functions-window.html 962 self.unsupported("PostgreSQL does not support IGNORE NULLS.") 963 return self.sql(expression.this) 964 965 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 966 # https://www.postgresql.org/docs/current/functions-window.html 967 self.unsupported("PostgreSQL does not support RESPECT NULLS.") 968 return self.sql(expression.this) 969 970 @unsupported_args("this") 971 def currentschema_sql(self, expression: exp.CurrentSchema) -> str: 972 return "CURRENT_SCHEMA" 973 974 def interval_sql(self, expression: exp.Interval) -> str: 975 unit = expression.text("unit").lower() 976 977 this = expression.this 978 if unit.startswith("quarter") and isinstance(this, exp.Literal): 979 this.replace(exp.Literal.string(int(this.to_py()) * 3)) 980 expression.args["unit"].replace(exp.var("MONTH")) 981 982 return super().interval_sql(expression) 983 984 def placeholder_sql(self, expression: exp.Placeholder) -> str: 985 if expression.args.get("jdbc"): 986 return "?" 987 988 this = f"({expression.name})" if expression.this else "" 989 return f"{self.NAMED_PLACEHOLDER_TOKEN}{this}s" 990 991 def arraycontains_sql(self, expression: exp.ArrayContains) -> str: 992 # Convert DuckDB's LIST_CONTAINS(array, value) to PostgreSQL 993 # DuckDB behavior: 994 # - LIST_CONTAINS([1,2,3], 2) -> true 995 # - LIST_CONTAINS([1,2,3], 4) -> false 996 # - LIST_CONTAINS([1,2,NULL], 4) -> false (not NULL) 997 # - LIST_CONTAINS([1,2,3], NULL) -> NULL 998 # 999 # PostgreSQL equivalent: CASE WHEN value IS NULL THEN NULL 1000 # ELSE COALESCE(value = ANY(array), FALSE) END 1001 value = expression.expression 1002 array = expression.this 1003 1004 coalesce_expr = exp.Coalesce( 1005 this=value.eq(exp.Any(this=exp.paren(expression=array, copy=False))), 1006 expressions=[exp.false()], 1007 ) 1008 1009 case_expr = ( 1010 exp.Case() 1011 .when(exp.Is(this=value, expression=exp.null()), exp.null(), copy=False) 1012 .else_(coalesce_expr, copy=False) 1013 ) 1014 1015 return self.sql(case_expr)
Whether the behavior of a / b depends on the types of a and b.
False means a / b is always float division.
True means a / b is integer division if both a and b are integers.
A NULL arg in CONCAT yields NULL by default, but in some dialects it yields an empty string.
Default NULL ordering method to use if not explicitly set.
Possible values: "nulls_are_small", "nulls_are_large", "nulls_are_last"
Whether table names can be referenced as columns (treated as structs).
BigQuery allows tables to be referenced as columns in queries, automatically treating them as struct values containing all the table's columns.
For example, in BigQuery: SELECT t FROM my_table AS t -- Returns entire row as a struct
Maps function expressions to their default output column name(s).
For example, in Postgres, generate_series function outputs a column named "generate_series" by default, so we map the ExplodingGenerateSeries expression to "generate_series" string.
Associates this dialect's time formats with their equivalent Python strftime formats.
Mapping of an escaped sequence (\n) to its unescaped version (
).
348 class Tokenizer(tokens.Tokenizer): 349 BIT_STRINGS = [("b'", "'"), ("B'", "'")] 350 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 351 BYTE_STRINGS = [("e'", "'"), ("E'", "'")] 352 BYTE_STRING_ESCAPES = ["'", "\\"] 353 HEREDOC_STRINGS = ["$"] 354 355 HEREDOC_TAG_IS_IDENTIFIER = True 356 HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER 357 358 KEYWORDS = { 359 **tokens.Tokenizer.KEYWORDS, 360 "~": TokenType.RLIKE, 361 "@@": TokenType.DAT, 362 "@>": TokenType.AT_GT, 363 "<@": TokenType.LT_AT, 364 "?&": TokenType.QMARK_AMP, 365 "?|": TokenType.QMARK_PIPE, 366 "#-": TokenType.HASH_DASH, 367 "|/": TokenType.PIPE_SLASH, 368 "||/": TokenType.DPIPE_SLASH, 369 "BEGIN": TokenType.BEGIN, 370 "BIGSERIAL": TokenType.BIGSERIAL, 371 "CONSTRAINT TRIGGER": TokenType.COMMAND, 372 "CSTRING": TokenType.PSEUDO_TYPE, 373 "DECLARE": TokenType.COMMAND, 374 "DO": TokenType.COMMAND, 375 "EXEC": TokenType.COMMAND, 376 "HSTORE": TokenType.HSTORE, 377 "INT8": TokenType.BIGINT, 378 "MONEY": TokenType.MONEY, 379 "NAME": TokenType.NAME, 380 "OID": TokenType.OBJECT_IDENTIFIER, 381 "ONLY": TokenType.ONLY, 382 "POINT": TokenType.POINT, 383 "REFRESH": TokenType.COMMAND, 384 "REINDEX": TokenType.COMMAND, 385 "RESET": TokenType.COMMAND, 386 "SERIAL": TokenType.SERIAL, 387 "SMALLSERIAL": TokenType.SMALLSERIAL, 388 "TEMP": TokenType.TEMPORARY, 389 "REGCLASS": TokenType.OBJECT_IDENTIFIER, 390 "REGCOLLATION": TokenType.OBJECT_IDENTIFIER, 391 "REGCONFIG": TokenType.OBJECT_IDENTIFIER, 392 "REGDICTIONARY": TokenType.OBJECT_IDENTIFIER, 393 "REGNAMESPACE": TokenType.OBJECT_IDENTIFIER, 394 "REGOPER": TokenType.OBJECT_IDENTIFIER, 395 "REGOPERATOR": TokenType.OBJECT_IDENTIFIER, 396 "REGPROC": TokenType.OBJECT_IDENTIFIER, 397 "REGPROCEDURE": TokenType.OBJECT_IDENTIFIER, 398 "REGROLE": TokenType.OBJECT_IDENTIFIER, 399 "REGTYPE": TokenType.OBJECT_IDENTIFIER, 400 "FLOAT": TokenType.DOUBLE, 401 "XML": TokenType.XML, 402 "VARIADIC": TokenType.VARIADIC, 403 "INOUT": TokenType.INOUT, 404 } 405 KEYWORDS.pop("/*+") 406 KEYWORDS.pop("DIV") 407 408 SINGLE_TOKENS = { 409 **tokens.Tokenizer.SINGLE_TOKENS, 410 "$": TokenType.HEREDOC_STRING, 411 } 412 413 VAR_SINGLE_TOKENS = {"$"}
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- RAW_STRINGS
- UNICODE_STRINGS
- IDENTIFIERS
- QUOTES
- STRING_ESCAPES
- ESCAPE_FOLLOW_CHARS
- IDENTIFIER_ESCAPES
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- NESTED_COMMENTS
- HINT_START
- TOKENS_PRECEDING_HINT
- WHITE_SPACE
- COMMANDS
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- COMMENTS
- dialect
- use_rs_tokenizer
- reset
- tokenize
- tokenize_rs
- size
- sql
- tokens
415 class Parser(parser.Parser): 416 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = True 417 418 PROPERTY_PARSERS = { 419 **parser.Parser.PROPERTY_PARSERS, 420 "SET": lambda self: self.expression(exp.SetConfigProperty, this=self._parse_set()), 421 } 422 PROPERTY_PARSERS.pop("INPUT") 423 424 PLACEHOLDER_PARSERS = { 425 **parser.Parser.PLACEHOLDER_PARSERS, 426 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder, jdbc=True), 427 TokenType.MOD: lambda self: self._parse_query_parameter(), 428 } 429 430 FUNCTIONS = { 431 **parser.Parser.FUNCTIONS, 432 "ARRAY_PREPEND": lambda args: exp.ArrayPrepend( 433 this=seq_get(args, 1), expression=seq_get(args, 0) 434 ), 435 "BIT_AND": exp.BitwiseAndAgg.from_arg_list, 436 "BIT_OR": exp.BitwiseOrAgg.from_arg_list, 437 "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, 438 "DATE_TRUNC": build_timestamp_trunc, 439 "DIV": lambda args: exp.cast( 440 binary_from_function(exp.IntDiv)(args), exp.DataType.Type.DECIMAL 441 ), 442 "GENERATE_SERIES": _build_generate_series, 443 "GET_BIT": lambda args: exp.Getbit( 444 this=seq_get(args, 0), expression=seq_get(args, 1), zero_is_msb=True 445 ), 446 "JSON_EXTRACT_PATH": build_json_extract_path(exp.JSONExtract), 447 "JSON_EXTRACT_PATH_TEXT": build_json_extract_path(exp.JSONExtractScalar), 448 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), encoding=seq_get(args, 1)), 449 "MAKE_TIME": exp.TimeFromParts.from_arg_list, 450 "MAKE_TIMESTAMP": exp.TimestampFromParts.from_arg_list, 451 "NOW": exp.CurrentTimestamp.from_arg_list, 452 "REGEXP_REPLACE": _build_regexp_replace, 453 "TO_CHAR": build_formatted_time(exp.TimeToStr, "postgres"), 454 "TO_DATE": build_formatted_time(exp.StrToDate, "postgres"), 455 "TO_TIMESTAMP": _build_to_timestamp, 456 "UNNEST": exp.Explode.from_arg_list, 457 "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)), 458 "SHA384": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(384)), 459 "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)), 460 "LEVENSHTEIN_LESS_EQUAL": _build_levenshtein_less_equal, 461 "JSON_OBJECT_AGG": lambda args: exp.JSONObjectAgg(expressions=args), 462 "JSONB_OBJECT_AGG": exp.JSONBObjectAgg.from_arg_list, 463 "WIDTH_BUCKET": lambda args: exp.WidthBucket( 464 this=seq_get(args, 0), threshold=seq_get(args, 1) 465 ) 466 if len(args) == 2 467 else exp.WidthBucket.from_arg_list(args), 468 } 469 470 NO_PAREN_FUNCTION_PARSERS = { 471 **parser.Parser.NO_PAREN_FUNCTION_PARSERS, 472 "VARIADIC": lambda self: self.expression(exp.Variadic, this=self._parse_bitwise()), 473 } 474 475 NO_PAREN_FUNCTIONS = { 476 **parser.Parser.NO_PAREN_FUNCTIONS, 477 TokenType.CURRENT_SCHEMA: exp.CurrentSchema, 478 } 479 480 FUNCTION_PARSERS = { 481 **parser.Parser.FUNCTION_PARSERS, 482 "DATE_PART": lambda self: self._parse_date_part(), 483 "JSON_AGG": lambda self: self.expression( 484 exp.JSONArrayAgg, 485 this=self._parse_lambda(), 486 order=self._parse_order(), 487 ), 488 "JSONB_EXISTS": lambda self: self._parse_jsonb_exists(), 489 } 490 491 BITWISE = { 492 **parser.Parser.BITWISE, 493 TokenType.HASH: exp.BitwiseXor, 494 } 495 496 EXPONENT = { 497 TokenType.CARET: exp.Pow, 498 } 499 500 RANGE_PARSERS = { 501 **parser.Parser.RANGE_PARSERS, 502 TokenType.DAMP: binary_range_parser(exp.ArrayOverlaps), 503 TokenType.DAT: lambda self, this: self.expression( 504 exp.MatchAgainst, this=self._parse_bitwise(), expressions=[this] 505 ), 506 } 507 508 STATEMENT_PARSERS = { 509 **parser.Parser.STATEMENT_PARSERS, 510 TokenType.END: lambda self: self._parse_commit_or_rollback(), 511 } 512 513 UNARY_PARSERS = { 514 **parser.Parser.UNARY_PARSERS, 515 # The `~` token is remapped from TILDE to RLIKE in Postgres due to the binary REGEXP LIKE operator 516 TokenType.RLIKE: lambda self: self.expression(exp.BitwiseNot, this=self._parse_unary()), 517 } 518 519 JSON_ARROWS_REQUIRE_JSON_TYPE = True 520 521 COLUMN_OPERATORS = { 522 **parser.Parser.COLUMN_OPERATORS, 523 TokenType.ARROW: lambda self, this, path: self.validate_expression( 524 build_json_extract_path( 525 exp.JSONExtract, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE 526 )([this, path]) 527 ), 528 TokenType.DARROW: lambda self, this, path: self.validate_expression( 529 build_json_extract_path( 530 exp.JSONExtractScalar, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE 531 )([this, path]) 532 ), 533 } 534 535 ARG_MODE_TOKENS = {TokenType.IN, TokenType.OUT, TokenType.INOUT, TokenType.VARIADIC} 536 537 def _parse_parameter_mode(self) -> t.Optional[TokenType]: 538 """ 539 Parse PostgreSQL function parameter mode (IN, OUT, INOUT, VARIADIC). 540 541 Disambiguates between mode keywords and identifiers with the same name: 542 - MODE TYPE → keyword is identifier (e.g., "out INT") 543 - MODE NAME TYPE → keyword is mode (e.g., "OUT x INT") 544 545 Returns: 546 Mode token type if current token is a mode keyword, None otherwise. 547 """ 548 if not self._match_set(self.ARG_MODE_TOKENS, advance=False) or not self._next: 549 return None 550 551 mode_token = self._curr 552 553 # Check Pattern 1: MODE TYPE 554 # Try parsing next token as a built-in type (not UDT) 555 # If successful, the keyword is an identifier, not a mode 556 is_followed_by_builtin_type = self._try_parse( 557 lambda: self._advance() # type: ignore 558 or self._parse_types(check_func=False, allow_identifiers=False), 559 retreat=True, 560 ) 561 if is_followed_by_builtin_type: 562 return None # Pattern: "out INT" → out is parameter name 563 564 # Check Pattern 2: MODE NAME TYPE 565 # If next token is an identifier, check if there's a type after it 566 # The type can be built-in or user-defined (allow_identifiers=True) 567 if self._next.token_type not in self.ID_VAR_TOKENS: 568 return None 569 570 is_followed_by_any_type = self._try_parse( 571 lambda: self._advance(2) # type: ignore 572 or self._parse_types(check_func=False, allow_identifiers=True), 573 retreat=True, 574 ) 575 576 if is_followed_by_any_type: 577 return mode_token.token_type # Pattern: "OUT x INT" → OUT is mode 578 579 return None 580 581 def _create_mode_constraint(self, param_mode: TokenType) -> exp.InOutColumnConstraint: 582 """ 583 Create parameter mode constraint for function parameters. 584 585 Args: 586 param_mode: The parameter mode token (IN, OUT, INOUT, or VARIADIC). 587 588 Returns: 589 InOutColumnConstraint expression representing the parameter mode. 590 """ 591 return self.expression( 592 exp.InOutColumnConstraint, 593 input_=(param_mode in {TokenType.IN, TokenType.INOUT}), 594 output=(param_mode in {TokenType.OUT, TokenType.INOUT}), 595 variadic=(param_mode == TokenType.VARIADIC), 596 ) 597 598 def _parse_function_parameter(self) -> t.Optional[exp.Expression]: 599 param_mode = self._parse_parameter_mode() 600 601 if param_mode: 602 self._advance() 603 604 # Parse parameter name and type 605 param_name = self._parse_id_var() 606 column_def = self._parse_column_def(this=param_name, computed_column=False) 607 608 # Attach mode as constraint 609 if param_mode and column_def: 610 constraint = self._create_mode_constraint(param_mode) 611 if not column_def.args.get("constraints"): 612 column_def.set("constraints", []) 613 column_def.args["constraints"].insert(0, constraint) 614 615 return column_def 616 617 def _parse_query_parameter(self) -> t.Optional[exp.Expression]: 618 this = ( 619 self._parse_wrapped(self._parse_id_var) 620 if self._match(TokenType.L_PAREN, advance=False) 621 else None 622 ) 623 self._match_text_seq("S") 624 return self.expression(exp.Placeholder, this=this) 625 626 def _parse_date_part(self) -> exp.Expression: 627 part = self._parse_type() 628 self._match(TokenType.COMMA) 629 value = self._parse_bitwise() 630 631 if part and isinstance(part, (exp.Column, exp.Literal)): 632 part = exp.var(part.name) 633 634 return self.expression(exp.Extract, this=part, expression=value) 635 636 def _parse_unique_key(self) -> t.Optional[exp.Expression]: 637 return None 638 639 def _parse_jsonb_exists(self) -> exp.JSONBExists: 640 return self.expression( 641 exp.JSONBExists, 642 this=self._parse_bitwise(), 643 path=self._match(TokenType.COMMA) 644 and self.dialect.to_json_path(self._parse_bitwise()), 645 ) 646 647 def _parse_generated_as_identity( 648 self, 649 ) -> ( 650 exp.GeneratedAsIdentityColumnConstraint 651 | exp.ComputedColumnConstraint 652 | exp.GeneratedAsRowColumnConstraint 653 ): 654 this = super()._parse_generated_as_identity() 655 656 if self._match_text_seq("STORED"): 657 this = self.expression(exp.ComputedColumnConstraint, this=this.expression) 658 659 return this 660 661 def _parse_user_defined_type( 662 self, identifier: exp.Identifier 663 ) -> t.Optional[exp.Expression]: 664 udt_type: exp.Identifier | exp.Dot = identifier 665 666 while self._match(TokenType.DOT): 667 part = self._parse_id_var() 668 if part: 669 udt_type = exp.Dot(this=udt_type, expression=part) 670 671 return exp.DataType.build(udt_type, udt=True)
Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
Arguments:
- error_level: The desired error level. Default: ErrorLevel.IMMEDIATE
- error_message_context: The amount of context to capture from a query string when displaying the error message (in number of characters). Default: 100
- max_errors: Maximum number of error messages to include in a raised ParseError. This is only relevant if error_level is ErrorLevel.RAISE. Default: 3
Inherited Members
- sqlglot.parser.Parser
- Parser
- STRUCT_TYPE_TOKENS
- NESTED_TYPE_TOKENS
- ENUM_TYPE_TOKENS
- AGGREGATE_TYPE_TOKENS
- TYPE_TOKENS
- SIGNED_TO_UNSIGNED_TYPE_TOKEN
- SUBQUERY_PREDICATES
- RESERVED_TOKENS
- DB_CREATABLES
- CREATABLES
- ALTERABLES
- ALIAS_TOKENS
- COLON_PLACEHOLDER_TOKENS
- ARRAY_CONSTRUCTORS
- COMMENT_TABLE_ALIAS_TOKENS
- UPDATE_ALIAS_TOKENS
- TRIM_TYPES
- FUNC_TOKENS
- CONJUNCTION
- ASSIGNMENT
- DISJUNCTION
- EQUALITY
- COMPARISON
- TERM
- FACTOR
- TIMES
- TIMESTAMPS
- SET_OPERATIONS
- JOIN_METHODS
- JOIN_SIDES
- JOIN_KINDS
- JOIN_HINTS
- LAMBDAS
- CAST_COLUMN_OPERATORS
- EXPRESSION_PARSERS
- STRING_PARSERS
- NUMERIC_PARSERS
- PRIMARY_PARSERS
- PIPE_SYNTAX_TRANSFORM_PARSERS
- CONSTRAINT_PARSERS
- ALTER_PARSERS
- ALTER_ALTER_PARSERS
- SCHEMA_UNNAMED_CONSTRAINTS
- INVALID_FUNC_NAME_TOKENS
- FUNCTIONS_WITH_ALIASED_ARGS
- KEY_VALUE_DEFINITIONS
- QUERY_MODIFIER_PARSERS
- QUERY_MODIFIER_TOKENS
- SET_PARSERS
- SHOW_PARSERS
- TYPE_LITERAL_PARSERS
- TYPE_CONVERTERS
- DDL_SELECT_TOKENS
- PRE_VOLATILE_TOKENS
- TRANSACTION_KIND
- TRANSACTION_CHARACTERISTICS
- CONFLICT_ACTIONS
- CREATE_SEQUENCE
- ISOLATED_LOADING_OPTIONS
- USABLES
- CAST_ACTIONS
- SCHEMA_BINDING_OPTIONS
- PROCEDURE_OPTIONS
- EXECUTE_AS_OPTIONS
- KEY_CONSTRAINT_OPTIONS
- WINDOW_EXCLUDE_OPTIONS
- INSERT_ALTERNATIVES
- CLONE_KEYWORDS
- HISTORICAL_DATA_PREFIX
- HISTORICAL_DATA_KIND
- OPCLASS_FOLLOW_KEYWORDS
- OPTYPE_FOLLOW_TOKENS
- TABLE_INDEX_HINT_TOKENS
- VIEW_ATTRIBUTES
- WINDOW_ALIAS_TOKENS
- WINDOW_BEFORE_PAREN_TOKENS
- WINDOW_SIDES
- JSON_KEY_VALUE_SEPARATOR_TOKENS
- FETCH_TOKENS
- ADD_CONSTRAINT_TOKENS
- DISTINCT_TOKENS
- UNNEST_OFFSET_ALIAS_TOKENS
- SELECT_START_TOKENS
- COPY_INTO_VARLEN_OPTIONS
- IS_JSON_PREDICATE_KIND
- ODBC_DATETIME_LITERALS
- ON_CONDITION_TOKENS
- PRIVILEGE_FOLLOW_TOKENS
- DESCRIBE_STYLES
- SET_ASSIGNMENT_DELIMITERS
- ANALYZE_STYLES
- ANALYZE_EXPRESSION_PARSERS
- PARTITION_KEYWORDS
- AMBIGUOUS_ALIAS_TOKENS
- OPERATION_MODIFIERS
- RECURSIVE_CTE_SEARCH_KIND
- MODIFIABLES
- STRICT_CAST
- PREFIXED_PIVOT_COLUMNS
- IDENTIFY_PIVOT_STRINGS
- LOG_DEFAULTS_TO_LN
- TABLESAMPLE_CSV
- DEFAULT_SAMPLING_METHOD
- SET_REQUIRES_ASSIGNMENT_DELIMITER
- TRIM_PATTERN_FIRST
- STRING_ALIASES
- MODIFIERS_ATTACHED_TO_SET_OP
- SET_OP_MODIFIERS
- NO_PAREN_IF_COMMANDS
- COLON_IS_VARIANT_EXTRACT
- VALUES_FOLLOWED_BY_PAREN
- SUPPORTS_IMPLICIT_UNNEST
- INTERVAL_SPANS
- SUPPORTS_PARTITION_SELECTION
- WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
- OPTIONAL_ALIAS_TOKEN_CTE
- ALTER_RENAME_REQUIRES_COLUMN
- ALTER_TABLE_PARTITIONS
- JOINS_HAVE_EQUAL_PRECEDENCE
- ZONE_AWARE_TIMESTAMP_CONSTRUCTOR
- MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS
- JSON_EXTRACT_REQUIRES_JSON_EXPRESSION
- ADD_JOIN_ON_TRUE
- error_level
- error_message_context
- max_errors
- dialect
- reset
- parse
- parse_into
- check_errors
- raise_error
- expression
- validate_expression
- parse_set_operation
- build_cast
- errors
- sql
673 class Generator(generator.Generator): 674 SINGLE_STRING_INTERVAL = True 675 RENAME_TABLE_WITH_DB = False 676 LOCKING_READS_SUPPORTED = True 677 JOIN_HINTS = False 678 TABLE_HINTS = False 679 QUERY_HINTS = False 680 NVL2_SUPPORTED = False 681 PARAMETER_TOKEN = "$" 682 NAMED_PLACEHOLDER_TOKEN = "%" 683 TABLESAMPLE_SIZE_IS_ROWS = False 684 TABLESAMPLE_SEED_KEYWORD = "REPEATABLE" 685 SUPPORTS_SELECT_INTO = True 686 JSON_TYPE_REQUIRED_FOR_EXTRACTION = True 687 SUPPORTS_UNLOGGED_TABLES = True 688 LIKE_PROPERTY_INSIDE_SCHEMA = True 689 MULTI_ARG_DISTINCT = False 690 CAN_IMPLEMENT_ARRAY_ANY = True 691 SUPPORTS_WINDOW_EXCLUDE = True 692 COPY_HAS_INTO_KEYWORD = False 693 ARRAY_CONCAT_IS_VAR_LEN = False 694 SUPPORTS_MEDIAN = False 695 ARRAY_SIZE_DIM_REQUIRED = True 696 SUPPORTS_BETWEEN_FLAGS = True 697 INOUT_SEPARATOR = "" # PostgreSQL uses "INOUT" (no space) 698 699 SUPPORTED_JSON_PATH_PARTS = { 700 exp.JSONPathKey, 701 exp.JSONPathRoot, 702 exp.JSONPathSubscript, 703 } 704 705 def lateral_sql(self, expression: exp.Lateral) -> str: 706 sql = super().lateral_sql(expression) 707 708 if expression.args.get("cross_apply") is not None: 709 sql = f"{sql} ON TRUE" 710 711 return sql 712 713 TYPE_MAPPING = { 714 **generator.Generator.TYPE_MAPPING, 715 exp.DataType.Type.TINYINT: "SMALLINT", 716 exp.DataType.Type.FLOAT: "REAL", 717 exp.DataType.Type.DOUBLE: "DOUBLE PRECISION", 718 exp.DataType.Type.BINARY: "BYTEA", 719 exp.DataType.Type.VARBINARY: "BYTEA", 720 exp.DataType.Type.ROWVERSION: "BYTEA", 721 exp.DataType.Type.DATETIME: "TIMESTAMP", 722 exp.DataType.Type.TIMESTAMPNTZ: "TIMESTAMP", 723 exp.DataType.Type.BLOB: "BYTEA", 724 } 725 726 TRANSFORMS = { 727 **generator.Generator.TRANSFORMS, 728 exp.AnyValue: _versioned_anyvalue_sql, 729 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 730 exp.ArrayFilter: filter_array_using_unnest, 731 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 732 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND", swap_params=True), 733 exp.BitwiseAndAgg: rename_func("BIT_AND"), 734 exp.BitwiseOrAgg: rename_func("BIT_OR"), 735 exp.BitwiseXor: lambda self, e: self.binary(e, "#"), 736 exp.BitwiseXorAgg: rename_func("BIT_XOR"), 737 exp.ColumnDef: transforms.preprocess([_auto_increment_to_serial, _serial_to_generated]), 738 exp.CurrentDate: no_paren_current_date_sql, 739 exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP", 740 exp.CurrentUser: lambda *_: "CURRENT_USER", 741 exp.DateAdd: _date_add_sql("+"), 742 exp.DateDiff: _date_diff_sql, 743 exp.DateStrToDate: datestrtodate_sql, 744 exp.DateSub: _date_add_sql("-"), 745 exp.Explode: rename_func("UNNEST"), 746 exp.ExplodingGenerateSeries: rename_func("GENERATE_SERIES"), 747 exp.Getbit: getbit_sql, 748 exp.GroupConcat: lambda self, e: groupconcat_sql( 749 self, e, func_name="STRING_AGG", within_group=False 750 ), 751 exp.IntDiv: rename_func("DIV"), 752 exp.JSONArrayAgg: lambda self, e: self.func( 753 "JSON_AGG", 754 self.sql(e, "this"), 755 suffix=f"{self.sql(e, 'order')})", 756 ), 757 exp.JSONExtract: _json_extract_sql("JSON_EXTRACT_PATH", "->"), 758 exp.JSONExtractScalar: _json_extract_sql("JSON_EXTRACT_PATH_TEXT", "->>"), 759 exp.JSONBExtract: lambda self, e: self.binary(e, "#>"), 760 exp.JSONBExtractScalar: lambda self, e: self.binary(e, "#>>"), 761 exp.JSONBContains: lambda self, e: self.binary(e, "?"), 762 exp.ParseJSON: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.JSON)), 763 exp.JSONPathKey: json_path_key_only_name, 764 exp.JSONPathRoot: lambda *_: "", 765 exp.JSONPathSubscript: lambda self, e: self.json_path_part(e.this), 766 exp.LastDay: no_last_day_sql, 767 exp.LogicalOr: rename_func("BOOL_OR"), 768 exp.LogicalAnd: rename_func("BOOL_AND"), 769 exp.Max: max_or_greatest, 770 exp.MapFromEntries: no_map_from_entries_sql, 771 exp.Min: min_or_least, 772 exp.Merge: merge_without_target_sql, 773 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 774 exp.PercentileCont: transforms.preprocess( 775 [transforms.add_within_group_for_percentiles] 776 ), 777 exp.PercentileDisc: transforms.preprocess( 778 [transforms.add_within_group_for_percentiles] 779 ), 780 exp.Pivot: no_pivot_sql, 781 exp.Rand: rename_func("RANDOM"), 782 exp.RegexpLike: lambda self, e: self.binary(e, "~"), 783 exp.RegexpILike: lambda self, e: self.binary(e, "~*"), 784 exp.RegexpReplace: lambda self, e: self.func( 785 "REGEXP_REPLACE", 786 e.this, 787 e.expression, 788 e.args.get("replacement"), 789 e.args.get("position"), 790 e.args.get("occurrence"), 791 regexp_replace_global_modifier(e), 792 ), 793 exp.Round: _round_sql, 794 exp.Select: transforms.preprocess( 795 [ 796 transforms.eliminate_semi_and_anti_joins, 797 transforms.eliminate_qualify, 798 ] 799 ), 800 exp.SHA2: sha256_sql, 801 exp.SHA2Digest: sha2_digest_sql, 802 exp.StrPosition: lambda self, e: strposition_sql(self, e, func_name="POSITION"), 803 exp.StrToDate: lambda self, e: self.func("TO_DATE", e.this, self.format_time(e)), 804 exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)), 805 exp.StructExtract: struct_extract_sql, 806 exp.Substring: _substring_sql, 807 exp.TimeFromParts: rename_func("MAKE_TIME"), 808 exp.TimestampFromParts: rename_func("MAKE_TIMESTAMP"), 809 exp.TimestampTrunc: timestamptrunc_sql(zone=True), 810 exp.TimeStrToTime: timestrtotime_sql, 811 exp.TimeToStr: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)), 812 exp.ToChar: lambda self, e: self.function_fallback_sql(e) 813 if e.args.get("format") 814 else self.tochar_sql(e), 815 exp.Trim: trim_sql, 816 exp.TryCast: no_trycast_sql, 817 exp.TsOrDsAdd: _date_add_sql("+"), 818 exp.TsOrDsDiff: _date_diff_sql, 819 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this), 820 exp.Uuid: lambda *_: "GEN_RANDOM_UUID()", 821 exp.TimeToUnix: lambda self, e: self.func( 822 "DATE_PART", exp.Literal.string("epoch"), e.this 823 ), 824 exp.VariancePop: rename_func("VAR_POP"), 825 exp.Variance: rename_func("VAR_SAMP"), 826 exp.Xor: bool_xor_sql, 827 exp.Unicode: rename_func("ASCII"), 828 exp.UnixToTime: _unix_to_time_sql, 829 exp.Levenshtein: _levenshtein_sql, 830 exp.JSONObjectAgg: rename_func("JSON_OBJECT_AGG"), 831 exp.JSONBObjectAgg: rename_func("JSONB_OBJECT_AGG"), 832 exp.CountIf: count_if_to_sum, 833 } 834 835 TRANSFORMS.pop(exp.CommentColumnConstraint) 836 837 PROPERTIES_LOCATION = { 838 **generator.Generator.PROPERTIES_LOCATION, 839 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 840 exp.TransientProperty: exp.Properties.Location.UNSUPPORTED, 841 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 842 } 843 844 def schemacommentproperty_sql(self, expression: exp.SchemaCommentProperty) -> str: 845 self.unsupported("Table comments are not supported in the CREATE statement") 846 return "" 847 848 def commentcolumnconstraint_sql(self, expression: exp.CommentColumnConstraint) -> str: 849 self.unsupported("Column comments are not supported in the CREATE statement") 850 return "" 851 852 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 853 # PostgreSQL places parameter modes BEFORE parameter name 854 param_constraint = expression.find(exp.InOutColumnConstraint) 855 856 if param_constraint: 857 mode_sql = self.sql(param_constraint) 858 param_constraint.pop() # Remove to prevent double-rendering 859 base_sql = super().columndef_sql(expression, sep) 860 return f"{mode_sql} {base_sql}" 861 862 return super().columndef_sql(expression, sep) 863 864 def unnest_sql(self, expression: exp.Unnest) -> str: 865 if len(expression.expressions) == 1: 866 arg = expression.expressions[0] 867 if isinstance(arg, exp.GenerateDateArray): 868 generate_series: exp.Expression = exp.GenerateSeries(**arg.args) 869 if isinstance(expression.parent, (exp.From, exp.Join)): 870 generate_series = ( 871 exp.select("value::date") 872 .from_(exp.Table(this=generate_series).as_("_t", table=["value"])) 873 .subquery(expression.args.get("alias") or "_unnested_generate_series") 874 ) 875 return self.sql(generate_series) 876 877 from sqlglot.optimizer.annotate_types import annotate_types 878 879 this = annotate_types(arg, dialect=self.dialect) 880 if this.is_type("array<json>"): 881 while isinstance(this, exp.Cast): 882 this = this.this 883 884 arg_as_json = self.sql(exp.cast(this, exp.DataType.Type.JSON)) 885 alias = self.sql(expression, "alias") 886 alias = f" AS {alias}" if alias else "" 887 888 if expression.args.get("offset"): 889 self.unsupported("Unsupported JSON_ARRAY_ELEMENTS with offset") 890 891 return f"JSON_ARRAY_ELEMENTS({arg_as_json}){alias}" 892 893 return super().unnest_sql(expression) 894 895 def bracket_sql(self, expression: exp.Bracket) -> str: 896 """Forms like ARRAY[1, 2, 3][3] aren't allowed; we need to wrap the ARRAY.""" 897 if isinstance(expression.this, exp.Array): 898 expression.set("this", exp.paren(expression.this, copy=False)) 899 900 return super().bracket_sql(expression) 901 902 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 903 this = self.sql(expression, "this") 904 expressions = [f"{self.sql(e)} @@ {this}" for e in expression.expressions] 905 sql = " OR ".join(expressions) 906 return f"({sql})" if len(expressions) > 1 else sql 907 908 def alterset_sql(self, expression: exp.AlterSet) -> str: 909 exprs = self.expressions(expression, flat=True) 910 exprs = f"({exprs})" if exprs else "" 911 912 access_method = self.sql(expression, "access_method") 913 access_method = f"ACCESS METHOD {access_method}" if access_method else "" 914 tablespace = self.sql(expression, "tablespace") 915 tablespace = f"TABLESPACE {tablespace}" if tablespace else "" 916 option = self.sql(expression, "option") 917 918 return f"SET {exprs}{access_method}{tablespace}{option}" 919 920 def datatype_sql(self, expression: exp.DataType) -> str: 921 if expression.is_type(exp.DataType.Type.ARRAY): 922 if expression.expressions: 923 values = self.expressions(expression, key="values", flat=True) 924 return f"{self.expressions(expression, flat=True)}[{values}]" 925 return "ARRAY" 926 927 if ( 928 expression.is_type(exp.DataType.Type.DOUBLE, exp.DataType.Type.FLOAT) 929 and expression.expressions 930 ): 931 # Postgres doesn't support precision for REAL and DOUBLE PRECISION types 932 return f"FLOAT({self.expressions(expression, flat=True)})" 933 934 return super().datatype_sql(expression) 935 936 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 937 this = expression.this 938 939 # Postgres casts DIV() to decimal for transpilation but when roundtripping it's superfluous 940 if isinstance(this, exp.IntDiv) and expression.to == exp.DataType.build("decimal"): 941 return self.sql(this) 942 943 return super().cast_sql(expression, safe_prefix=safe_prefix) 944 945 def array_sql(self, expression: exp.Array) -> str: 946 exprs = expression.expressions 947 func_name = self.normalize_func("ARRAY") 948 949 if isinstance(seq_get(exprs, 0), exp.Select): 950 return f"{func_name}({self.sql(exprs[0])})" 951 952 return f"{func_name}{inline_array_sql(self, expression)}" 953 954 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 955 return f"GENERATED ALWAYS AS ({self.sql(expression, 'this')}) STORED" 956 957 def isascii_sql(self, expression: exp.IsAscii) -> str: 958 return f"({self.sql(expression.this)} ~ '^[[:ascii:]]*$')" 959 960 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 961 # https://www.postgresql.org/docs/current/functions-window.html 962 self.unsupported("PostgreSQL does not support IGNORE NULLS.") 963 return self.sql(expression.this) 964 965 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 966 # https://www.postgresql.org/docs/current/functions-window.html 967 self.unsupported("PostgreSQL does not support RESPECT NULLS.") 968 return self.sql(expression.this) 969 970 @unsupported_args("this") 971 def currentschema_sql(self, expression: exp.CurrentSchema) -> str: 972 return "CURRENT_SCHEMA" 973 974 def interval_sql(self, expression: exp.Interval) -> str: 975 unit = expression.text("unit").lower() 976 977 this = expression.this 978 if unit.startswith("quarter") and isinstance(this, exp.Literal): 979 this.replace(exp.Literal.string(int(this.to_py()) * 3)) 980 expression.args["unit"].replace(exp.var("MONTH")) 981 982 return super().interval_sql(expression) 983 984 def placeholder_sql(self, expression: exp.Placeholder) -> str: 985 if expression.args.get("jdbc"): 986 return "?" 987 988 this = f"({expression.name})" if expression.this else "" 989 return f"{self.NAMED_PLACEHOLDER_TOKEN}{this}s" 990 991 def arraycontains_sql(self, expression: exp.ArrayContains) -> str: 992 # Convert DuckDB's LIST_CONTAINS(array, value) to PostgreSQL 993 # DuckDB behavior: 994 # - LIST_CONTAINS([1,2,3], 2) -> true 995 # - LIST_CONTAINS([1,2,3], 4) -> false 996 # - LIST_CONTAINS([1,2,NULL], 4) -> false (not NULL) 997 # - LIST_CONTAINS([1,2,3], NULL) -> NULL 998 # 999 # PostgreSQL equivalent: CASE WHEN value IS NULL THEN NULL 1000 # ELSE COALESCE(value = ANY(array), FALSE) END 1001 value = expression.expression 1002 array = expression.this 1003 1004 coalesce_expr = exp.Coalesce( 1005 this=value.eq(exp.Any(this=exp.paren(expression=array, copy=False))), 1006 expressions=[exp.false()], 1007 ) 1008 1009 case_expr = ( 1010 exp.Case() 1011 .when(exp.Is(this=value, expression=exp.null()), exp.null(), copy=False) 1012 .else_(coalesce_expr, copy=False) 1013 ) 1014 1015 return self.sql(case_expr)
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
852 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 853 # PostgreSQL places parameter modes BEFORE parameter name 854 param_constraint = expression.find(exp.InOutColumnConstraint) 855 856 if param_constraint: 857 mode_sql = self.sql(param_constraint) 858 param_constraint.pop() # Remove to prevent double-rendering 859 base_sql = super().columndef_sql(expression, sep) 860 return f"{mode_sql} {base_sql}" 861 862 return super().columndef_sql(expression, sep)
864 def unnest_sql(self, expression: exp.Unnest) -> str: 865 if len(expression.expressions) == 1: 866 arg = expression.expressions[0] 867 if isinstance(arg, exp.GenerateDateArray): 868 generate_series: exp.Expression = exp.GenerateSeries(**arg.args) 869 if isinstance(expression.parent, (exp.From, exp.Join)): 870 generate_series = ( 871 exp.select("value::date") 872 .from_(exp.Table(this=generate_series).as_("_t", table=["value"])) 873 .subquery(expression.args.get("alias") or "_unnested_generate_series") 874 ) 875 return self.sql(generate_series) 876 877 from sqlglot.optimizer.annotate_types import annotate_types 878 879 this = annotate_types(arg, dialect=self.dialect) 880 if this.is_type("array<json>"): 881 while isinstance(this, exp.Cast): 882 this = this.this 883 884 arg_as_json = self.sql(exp.cast(this, exp.DataType.Type.JSON)) 885 alias = self.sql(expression, "alias") 886 alias = f" AS {alias}" if alias else "" 887 888 if expression.args.get("offset"): 889 self.unsupported("Unsupported JSON_ARRAY_ELEMENTS with offset") 890 891 return f"JSON_ARRAY_ELEMENTS({arg_as_json}){alias}" 892 893 return super().unnest_sql(expression)
895 def bracket_sql(self, expression: exp.Bracket) -> str: 896 """Forms like ARRAY[1, 2, 3][3] aren't allowed; we need to wrap the ARRAY.""" 897 if isinstance(expression.this, exp.Array): 898 expression.set("this", exp.paren(expression.this, copy=False)) 899 900 return super().bracket_sql(expression)
Forms like ARRAY[1, 2, 3][3] aren't allowed; we need to wrap the ARRAY.
908 def alterset_sql(self, expression: exp.AlterSet) -> str: 909 exprs = self.expressions(expression, flat=True) 910 exprs = f"({exprs})" if exprs else "" 911 912 access_method = self.sql(expression, "access_method") 913 access_method = f"ACCESS METHOD {access_method}" if access_method else "" 914 tablespace = self.sql(expression, "tablespace") 915 tablespace = f"TABLESPACE {tablespace}" if tablespace else "" 916 option = self.sql(expression, "option") 917 918 return f"SET {exprs}{access_method}{tablespace}{option}"
920 def datatype_sql(self, expression: exp.DataType) -> str: 921 if expression.is_type(exp.DataType.Type.ARRAY): 922 if expression.expressions: 923 values = self.expressions(expression, key="values", flat=True) 924 return f"{self.expressions(expression, flat=True)}[{values}]" 925 return "ARRAY" 926 927 if ( 928 expression.is_type(exp.DataType.Type.DOUBLE, exp.DataType.Type.FLOAT) 929 and expression.expressions 930 ): 931 # Postgres doesn't support precision for REAL and DOUBLE PRECISION types 932 return f"FLOAT({self.expressions(expression, flat=True)})" 933 934 return super().datatype_sql(expression)
936 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 937 this = expression.this 938 939 # Postgres casts DIV() to decimal for transpilation but when roundtripping it's superfluous 940 if isinstance(this, exp.IntDiv) and expression.to == exp.DataType.build("decimal"): 941 return self.sql(this) 942 943 return super().cast_sql(expression, safe_prefix=safe_prefix)
945 def array_sql(self, expression: exp.Array) -> str: 946 exprs = expression.expressions 947 func_name = self.normalize_func("ARRAY") 948 949 if isinstance(seq_get(exprs, 0), exp.Select): 950 return f"{func_name}({self.sql(exprs[0])})" 951 952 return f"{func_name}{inline_array_sql(self, expression)}"
974 def interval_sql(self, expression: exp.Interval) -> str: 975 unit = expression.text("unit").lower() 976 977 this = expression.this 978 if unit.startswith("quarter") and isinstance(this, exp.Literal): 979 this.replace(exp.Literal.string(int(this.to_py()) * 3)) 980 expression.args["unit"].replace(exp.var("MONTH")) 981 982 return super().interval_sql(expression)
991 def arraycontains_sql(self, expression: exp.ArrayContains) -> str: 992 # Convert DuckDB's LIST_CONTAINS(array, value) to PostgreSQL 993 # DuckDB behavior: 994 # - LIST_CONTAINS([1,2,3], 2) -> true 995 # - LIST_CONTAINS([1,2,3], 4) -> false 996 # - LIST_CONTAINS([1,2,NULL], 4) -> false (not NULL) 997 # - LIST_CONTAINS([1,2,3], NULL) -> NULL 998 # 999 # PostgreSQL equivalent: CASE WHEN value IS NULL THEN NULL 1000 # ELSE COALESCE(value = ANY(array), FALSE) END 1001 value = expression.expression 1002 array = expression.this 1003 1004 coalesce_expr = exp.Coalesce( 1005 this=value.eq(exp.Any(this=exp.paren(expression=array, copy=False))), 1006 expressions=[exp.false()], 1007 ) 1008 1009 case_expr = ( 1010 exp.Case() 1011 .when(exp.Is(this=value, expression=exp.null()), exp.null(), copy=False) 1012 .else_(coalesce_expr, copy=False) 1013 ) 1014 1015 return self.sql(case_expr)
Inherited Members
- sqlglot.generator.Generator
- Generator
- NULL_ORDERING_SUPPORTED
- IGNORE_NULLS_IN_FUNC
- EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
- WRAP_DERIVED_VALUES
- CREATE_FUNCTION_RETURN_AS
- MATCHED_BY_SOURCE
- INTERVAL_ALLOWS_PLURAL_FORM
- LIMIT_FETCH
- LIMIT_ONLY_LITERALS
- GROUPINGS_SEP
- INDEX_ON
- DIRECTED_JOINS
- QUERY_HINT_SEP
- IS_BOOL_ALLOWED
- DUPLICATE_KEY_UPDATE_WITH_SET
- LIMIT_IS_TOP
- RETURNING_END
- EXTRACT_ALLOWS_QUOTES
- TZ_TO_WITH_TIME_ZONE
- VALUES_AS_TABLE
- ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
- UNNEST_WITH_ORDINALITY
- AGGREGATE_FILTER_SUPPORTED
- SEMI_ANTI_JOIN_WITH_SIDE
- COMPUTED_COLUMN_WITH_TYPE
- SUPPORTS_TABLE_COPY
- TABLESAMPLE_REQUIRES_PARENS
- TABLESAMPLE_KEYWORDS
- TABLESAMPLE_WITH_METHOD
- COLLATE_IS_FUNC
- DATA_TYPE_SPECIFIERS_ALLOWED
- ENSURE_BOOLS
- CTE_RECURSIVE_KEYWORD_REQUIRED
- SUPPORTS_SINGLE_ARG_CONCAT
- LAST_DAY_SUPPORTS_DATE_PART
- SUPPORTS_TABLE_ALIAS_COLUMNS
- UNPIVOT_ALIASES_ARE_IDENTIFIERS
- JSON_KEY_VALUE_PAIR_SEP
- INSERT_OVERWRITE
- SUPPORTS_CREATE_TABLE_LIKE
- JSON_PATH_BRACKETED_KEY_SUPPORTED
- JSON_PATH_SINGLE_QUOTE_ESCAPE
- SUPPORTS_TO_NUMBER
- SET_OP_MODIFIERS
- COPY_PARAMS_ARE_WRAPPED
- COPY_PARAMS_EQ_REQUIRED
- UNICODE_SUBSTITUTE
- STAR_EXCEPT
- HEX_FUNC
- WITH_PROPERTIES_PREFIX
- QUOTE_JSON_PATH
- PAD_FILL_PATTERN_IS_REQUIRED
- SUPPORTS_EXPLODING_PROJECTIONS
- SUPPORTS_CONVERT_TIMEZONE
- SUPPORTS_UNIX_SECONDS
- ALTER_SET_WRAPPED
- NORMALIZE_EXTRACT_DATE_PARTS
- PARSE_JSON_NAME
- ARRAY_SIZE_NAME
- ALTER_SET_TYPE
- SUPPORTS_LIKE_QUANTIFIERS
- MATCH_AGAINST_TABLE_PREFIX
- SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD
- UPDATE_STATEMENT_SUPPORTS_FROM
- UNSUPPORTED_TYPES
- TIME_PART_SINGULARS
- TOKEN_MAPPING
- STRUCT_DELIMITER
- EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
- RESERVED_KEYWORDS
- WITH_SEPARATED_COMMENTS
- EXCLUDE_COMMENTS
- UNWRAPPED_INTERVAL_VALUES
- PARAMETERIZABLE_TEXT_TYPES
- EXPRESSIONS_WITHOUT_NESTED_CTES
- RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS
- SAFE_JSON_PATH_KEY_RE
- SENTINEL_LINE_BREAK
- pretty
- identify
- normalize
- pad
- unsupported_level
- max_unsupported
- leading_comma
- max_text_width
- comments
- dialect
- normalize_functions
- unsupported_messages
- generate
- preprocess
- unsupported
- sep
- seg
- sanitize_comment
- maybe_comment
- wrap
- no_identify
- normalize_func
- indent
- sql
- uncache_sql
- cache_sql
- characterset_sql
- column_parts
- column_sql
- pseudocolumn_sql
- columnposition_sql
- columnconstraint_sql
- autoincrementcolumnconstraint_sql
- compresscolumnconstraint_sql
- generatedasidentitycolumnconstraint_sql
- generatedasrowcolumnconstraint_sql
- periodforsystemtimeconstraint_sql
- notnullcolumnconstraint_sql
- primarykeycolumnconstraint_sql
- uniquecolumnconstraint_sql
- inoutcolumnconstraint_sql
- createable_sql
- create_sql
- sequenceproperties_sql
- clone_sql
- describe_sql
- heredoc_sql
- prepend_ctes
- with_sql
- cte_sql
- tablealias_sql
- bitstring_sql
- hexstring_sql
- bytestring_sql
- unicodestring_sql
- rawstring_sql
- datatypeparam_sql
- directory_sql
- delete_sql
- drop_sql
- set_operation
- set_operations
- fetch_sql
- limitoptions_sql
- filter_sql
- hint_sql
- indexparameters_sql
- index_sql
- identifier_sql
- hex_sql
- lowerhex_sql
- inputoutputformat_sql
- national_sql
- partition_sql
- properties_sql
- root_properties
- properties
- with_properties
- locate_properties
- property_name
- property_sql
- likeproperty_sql
- fallbackproperty_sql
- journalproperty_sql
- freespaceproperty_sql
- checksumproperty_sql
- mergeblockratioproperty_sql
- datablocksizeproperty_sql
- blockcompressionproperty_sql
- isolatedloadingproperty_sql
- partitionboundspec_sql
- partitionedofproperty_sql
- lockingproperty_sql
- withdataproperty_sql
- withsystemversioningproperty_sql
- insert_sql
- introducer_sql
- kill_sql
- pseudotype_sql
- objectidentifier_sql
- onconflict_sql
- returning_sql
- rowformatdelimitedproperty_sql
- withtablehint_sql
- indextablehint_sql
- historicaldata_sql
- table_parts
- table_sql
- tablefromrows_sql
- tablesample_sql
- pivot_sql
- version_sql
- tuple_sql
- update_sql
- values_sql
- var_sql
- into_sql
- from_sql
- groupingsets_sql
- rollup_sql
- rollupindex_sql
- rollupproperty_sql
- cube_sql
- group_sql
- having_sql
- connect_sql
- prior_sql
- join_sql
- lambda_sql
- lateral_op
- limit_sql
- offset_sql
- setitem_sql
- set_sql
- queryband_sql
- pragma_sql
- lock_sql
- literal_sql
- escape_str
- loaddata_sql
- null_sql
- boolean_sql
- booland_sql
- boolor_sql
- order_sql
- withfill_sql
- cluster_sql
- distribute_sql
- sort_sql
- ordered_sql
- matchrecognizemeasure_sql
- matchrecognize_sql
- query_modifiers
- options_modifier
- for_modifiers
- queryoption_sql
- offset_limit_modifiers
- after_limit_modifiers
- select_sql
- schema_sql
- schema_columns_sql
- star_sql
- parameter_sql
- sessionparameter_sql
- subquery_sql
- qualify_sql
- prewhere_sql
- where_sql
- window_sql
- partition_by_sql
- windowspec_sql
- withingroup_sql
- between_sql
- bracket_offset_expressions
- all_sql
- any_sql
- exists_sql
- case_sql
- constraint_sql
- nextvaluefor_sql
- extract_sql
- trim_sql
- convert_concat_args
- concat_sql
- concatws_sql
- check_sql
- foreignkey_sql
- primarykey_sql
- if_sql
- jsonkeyvalue_sql
- jsonpath_sql
- json_path_part
- formatjson_sql
- formatphrase_sql
- jsonobject_sql
- jsonobjectagg_sql
- jsonarray_sql
- jsonarrayagg_sql
- jsoncolumndef_sql
- jsonschema_sql
- jsontable_sql
- openjsoncolumndef_sql
- openjson_sql
- in_sql
- in_unnest_op
- return_sql
- reference_sql
- anonymous_sql
- paren_sql
- neg_sql
- not_sql
- alias_sql
- pivotalias_sql
- aliases_sql
- atindex_sql
- attimezone_sql
- fromtimezone_sql
- add_sql
- and_sql
- or_sql
- xor_sql
- connector_sql
- bitwiseand_sql
- bitwiseleftshift_sql
- bitwisenot_sql
- bitwiseor_sql
- bitwiserightshift_sql
- bitwisexor_sql
- strtotime_sql
- currentdate_sql
- collate_sql
- command_sql
- comment_sql
- mergetreettlaction_sql
- mergetreettl_sql
- transaction_sql
- commit_sql
- rollback_sql
- altercolumn_sql
- alterindex_sql
- alterdiststyle_sql
- altersortkey_sql
- alterrename_sql
- renamecolumn_sql
- alter_sql
- altersession_sql
- add_column_sql
- droppartition_sql
- addconstraint_sql
- addpartition_sql
- distinct_sql
- havingmax_sql
- intdiv_sql
- dpipe_sql
- div_sql
- safedivide_sql
- overlaps_sql
- distance_sql
- dot_sql
- eq_sql
- propertyeq_sql
- escape_sql
- glob_sql
- gt_sql
- gte_sql
- is_sql
- like_sql
- ilike_sql
- match_sql
- similarto_sql
- lt_sql
- lte_sql
- mod_sql
- mul_sql
- neq_sql
- nullsafeeq_sql
- nullsafeneq_sql
- sub_sql
- trycast_sql
- jsoncast_sql
- try_sql
- log_sql
- use_sql
- binary
- ceil_floor
- function_fallback_sql
- func
- format_args
- too_wide
- format_time
- expressions
- op_expressions
- naked_property
- tag_sql
- token_sql
- userdefinedfunction_sql
- joinhint_sql
- kwarg_sql
- when_sql
- whens_sql
- merge_sql
- tochar_sql
- tonumber_sql
- dictproperty_sql
- dictrange_sql
- dictsubproperty_sql
- duplicatekeyproperty_sql
- uniquekeyproperty_sql
- distributedbyproperty_sql
- oncluster_sql
- clusteredbyproperty_sql
- anyvalue_sql
- querytransform_sql
- indexconstraintoption_sql
- checkcolumnconstraint_sql
- indexcolumnconstraint_sql
- nvl2_sql
- comprehension_sql
- columnprefix_sql
- opclass_sql
- predict_sql
- generateembedding_sql
- mltranslate_sql
- mlforecast_sql
- featuresattime_sql
- vectorsearch_sql
- forin_sql
- refresh_sql
- toarray_sql
- tsordstotime_sql
- tsordstotimestamp_sql
- tsordstodatetime_sql
- tsordstodate_sql
- unixdate_sql
- lastday_sql
- dateadd_sql
- arrayany_sql
- struct_sql
- partitionrange_sql
- truncatetable_sql
- convert_sql
- copyparameter_sql
- credentials_sql
- copy_sql
- semicolon_sql
- datadeletionproperty_sql
- maskingpolicycolumnconstraint_sql
- gapfill_sql
- scope_resolution
- scoperesolution_sql
- parsejson_sql
- rand_sql
- changes_sql
- pad_sql
- summarize_sql
- explodinggenerateseries_sql
- converttimezone_sql
- json_sql
- jsonvalue_sql
- conditionalinsert_sql
- multitableinserts_sql
- oncondition_sql
- jsonextractquote_sql
- jsonexists_sql
- arrayagg_sql
- slice_sql
- apply_sql
- grant_sql
- revoke_sql
- grantprivilege_sql
- grantprincipal_sql
- columns_sql
- overlay_sql
- todouble_sql
- string_sql
- median_sql
- overflowtruncatebehavior_sql
- unixseconds_sql
- arraysize_sql
- attach_sql
- detach_sql
- attachoption_sql
- watermarkcolumnconstraint_sql
- encodeproperty_sql
- includeproperty_sql
- xmlelement_sql
- xmlkeyvalueoption_sql
- partitionbyrangeproperty_sql
- partitionbyrangepropertydynamic_sql
- unpivotcolumns_sql
- analyzesample_sql
- analyzestatistics_sql
- analyzehistogram_sql
- analyzedelete_sql
- analyzelistchainedrows_sql
- analyzevalidate_sql
- analyze_sql
- xmltable_sql
- xmlnamespace_sql
- export_sql
- declare_sql
- declareitem_sql
- recursivewithsearch_sql
- parameterizedagg_sql
- anonymousaggfunc_sql
- combinedaggfunc_sql
- combinedparameterizedagg_sql
- show_sql
- install_sql
- get_put_sql
- translatecharacters_sql
- decodecase_sql
- semanticview_sql
- getextract_sql
- datefromunixdate_sql
- space_sql
- buildproperty_sql
- refreshtriggerproperty_sql
- modelattribute_sql
- directorystage_sql
- uuid_sql
- initcap_sql
- localtime_sql
- localtimestamp_sql
- weekstart_sql
- chr_sql