sqlglot.dialects.snowflake
1from __future__ import annotations 2 3import typing as t 4 5from sqlglot import exp, generator, parser, tokens, transforms 6from sqlglot.dialects.dialect import ( 7 Dialect, 8 NormalizationStrategy, 9 binary_from_function, 10 build_default_decimal_type, 11 build_timestamp_from_parts, 12 date_delta_sql, 13 date_trunc_to_time, 14 datestrtodate_sql, 15 build_formatted_time, 16 if_sql, 17 inline_array_sql, 18 max_or_greatest, 19 min_or_least, 20 rename_func, 21 timestamptrunc_sql, 22 timestrtotime_sql, 23 var_map_sql, 24 map_date_part, 25) 26from sqlglot.helper import flatten, is_float, is_int, seq_get 27from sqlglot.tokens import TokenType 28 29if t.TYPE_CHECKING: 30 from sqlglot._typing import E 31 32 33# from https://docs.snowflake.com/en/sql-reference/functions/to_timestamp.html 34def _build_datetime( 35 name: str, kind: exp.DataType.Type, safe: bool = False 36) -> t.Callable[[t.List], exp.Func]: 37 def _builder(args: t.List) -> exp.Func: 38 value = seq_get(args, 0) 39 int_value = value is not None and is_int(value.name) 40 41 if isinstance(value, exp.Literal): 42 # Converts calls like `TO_TIME('01:02:03')` into casts 43 if len(args) == 1 and value.is_string and not int_value: 44 return exp.cast(value, kind) 45 46 # Handles `TO_TIMESTAMP(str, fmt)` and `TO_TIMESTAMP(num, scale)` as special 47 # cases so we can transpile them, since they're relatively common 48 if kind == exp.DataType.Type.TIMESTAMP: 49 if int_value: 50 return exp.UnixToTime(this=value, scale=seq_get(args, 1)) 51 if not is_float(value.this): 52 return build_formatted_time(exp.StrToTime, "snowflake")(args) 53 54 if kind == exp.DataType.Type.DATE and not int_value: 55 formatted_exp = build_formatted_time(exp.TsOrDsToDate, "snowflake")(args) 56 formatted_exp.set("safe", safe) 57 return formatted_exp 58 59 return exp.Anonymous(this=name, expressions=args) 60 61 return _builder 62 63 64def _build_object_construct(args: t.List) -> t.Union[exp.StarMap, exp.Struct]: 65 expression = parser.build_var_map(args) 66 67 if isinstance(expression, exp.StarMap): 68 return expression 69 70 return exp.Struct( 71 expressions=[ 72 exp.PropertyEQ(this=k, expression=v) for k, v in zip(expression.keys, expression.values) 73 ] 74 ) 75 76 77def _build_datediff(args: t.List) -> exp.DateDiff: 78 return exp.DateDiff( 79 this=seq_get(args, 2), expression=seq_get(args, 1), unit=map_date_part(seq_get(args, 0)) 80 ) 81 82 83def _build_date_time_add(expr_type: t.Type[E]) -> t.Callable[[t.List], E]: 84 def _builder(args: t.List) -> E: 85 return expr_type( 86 this=seq_get(args, 2), 87 expression=seq_get(args, 1), 88 unit=map_date_part(seq_get(args, 0)), 89 ) 90 91 return _builder 92 93 94# https://docs.snowflake.com/en/sql-reference/functions/div0 95def _build_if_from_div0(args: t.List) -> exp.If: 96 cond = exp.EQ(this=seq_get(args, 1), expression=exp.Literal.number(0)).and_( 97 exp.Is(this=seq_get(args, 0), expression=exp.null()).not_() 98 ) 99 true = exp.Literal.number(0) 100 false = exp.Div(this=seq_get(args, 0), expression=seq_get(args, 1)) 101 return exp.If(this=cond, true=true, false=false) 102 103 104# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 105def _build_if_from_zeroifnull(args: t.List) -> exp.If: 106 cond = exp.Is(this=seq_get(args, 0), expression=exp.Null()) 107 return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0)) 108 109 110# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 111def _build_if_from_nullifzero(args: t.List) -> exp.If: 112 cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0)) 113 return exp.If(this=cond, true=exp.Null(), false=seq_get(args, 0)) 114 115 116def _regexpilike_sql(self: Snowflake.Generator, expression: exp.RegexpILike) -> str: 117 flag = expression.text("flag") 118 119 if "i" not in flag: 120 flag += "i" 121 122 return self.func( 123 "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag) 124 ) 125 126 127def _build_regexp_replace(args: t.List) -> exp.RegexpReplace: 128 regexp_replace = exp.RegexpReplace.from_arg_list(args) 129 130 if not regexp_replace.args.get("replacement"): 131 regexp_replace.set("replacement", exp.Literal.string("")) 132 133 return regexp_replace 134 135 136def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[Snowflake.Parser], exp.Show]: 137 def _parse(self: Snowflake.Parser) -> exp.Show: 138 return self._parse_show_snowflake(*args, **kwargs) 139 140 return _parse 141 142 143def _date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc: 144 trunc = date_trunc_to_time(args) 145 trunc.set("unit", map_date_part(trunc.args["unit"])) 146 return trunc 147 148 149def _unqualify_unpivot_columns(expression: exp.Expression) -> exp.Expression: 150 """ 151 Snowflake doesn't allow columns referenced in UNPIVOT to be qualified, 152 so we need to unqualify them. 153 154 Example: 155 >>> from sqlglot import parse_one 156 >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))") 157 >>> print(_unqualify_unpivot_columns(expr).sql(dialect="snowflake")) 158 SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april)) 159 """ 160 if isinstance(expression, exp.Pivot) and expression.unpivot: 161 expression = transforms.unqualify_columns(expression) 162 163 return expression 164 165 166def _flatten_structured_types_unless_iceberg(expression: exp.Expression) -> exp.Expression: 167 assert isinstance(expression, exp.Create) 168 169 def _flatten_structured_type(expression: exp.DataType) -> exp.DataType: 170 if expression.this in exp.DataType.NESTED_TYPES: 171 expression.set("expressions", None) 172 return expression 173 174 props = expression.args.get("properties") 175 if isinstance(expression.this, exp.Schema) and not (props and props.find(exp.IcebergProperty)): 176 for schema_expression in expression.this.expressions: 177 if isinstance(schema_expression, exp.ColumnDef): 178 column_type = schema_expression.kind 179 if isinstance(column_type, exp.DataType): 180 column_type.transform(_flatten_structured_type, copy=False) 181 182 return expression 183 184 185def _unnest_generate_date_array(expression: exp.Expression) -> exp.Expression: 186 if isinstance(expression, exp.Select): 187 for unnest in expression.find_all(exp.Unnest): 188 if ( 189 isinstance(unnest.parent, (exp.From, exp.Join)) 190 and len(unnest.expressions) == 1 191 and isinstance(unnest.expressions[0], exp.GenerateDateArray) 192 ): 193 generate_date_array = unnest.expressions[0] 194 start = generate_date_array.args.get("start") 195 end = generate_date_array.args.get("end") 196 step = generate_date_array.args.get("step") 197 198 if not start or not end or not isinstance(step, exp.Interval) or step.name != "1": 199 continue 200 201 unit = step.args.get("unit") 202 203 unnest_alias = unnest.args.get("alias") 204 if unnest_alias: 205 unnest_alias = unnest_alias.copy() 206 sequence_value_name = seq_get(unnest_alias.columns, 0) or "value" 207 else: 208 sequence_value_name = "value" 209 210 # We'll add the next sequence value to the starting date and project the result 211 date_add = _build_date_time_add(exp.DateAdd)( 212 [unit, exp.cast(sequence_value_name, "int"), exp.cast(start, "date")] 213 ).as_(sequence_value_name) 214 215 # We use DATEDIFF to compute the number of sequence values needed 216 number_sequence = Snowflake.Parser.FUNCTIONS["ARRAY_GENERATE_RANGE"]( 217 [exp.Literal.number(0), _build_datediff([unit, start, end]) + 1] 218 ) 219 220 unnest.set("expressions", [number_sequence]) 221 unnest.replace(exp.select(date_add).from_(unnest.copy()).subquery(unnest_alias)) 222 223 return expression 224 225 226class Snowflake(Dialect): 227 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 228 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 229 NULL_ORDERING = "nulls_are_large" 230 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 231 SUPPORTS_USER_DEFINED_TYPES = False 232 SUPPORTS_SEMI_ANTI_JOIN = False 233 PREFER_CTE_ALIAS_COLUMN = True 234 TABLESAMPLE_SIZE_IS_PERCENT = True 235 COPY_PARAMS_ARE_CSV = False 236 ARRAY_AGG_INCLUDES_NULLS = None 237 238 TIME_MAPPING = { 239 "YYYY": "%Y", 240 "yyyy": "%Y", 241 "YY": "%y", 242 "yy": "%y", 243 "MMMM": "%B", 244 "mmmm": "%B", 245 "MON": "%b", 246 "mon": "%b", 247 "MM": "%m", 248 "mm": "%m", 249 "DD": "%d", 250 "dd": "%-d", 251 "DY": "%a", 252 "dy": "%w", 253 "HH24": "%H", 254 "hh24": "%H", 255 "HH12": "%I", 256 "hh12": "%I", 257 "MI": "%M", 258 "mi": "%M", 259 "SS": "%S", 260 "ss": "%S", 261 "FF": "%f", 262 "ff": "%f", 263 "FF6": "%f", 264 "ff6": "%f", 265 } 266 267 def quote_identifier(self, expression: E, identify: bool = True) -> E: 268 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 269 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 270 if ( 271 isinstance(expression, exp.Identifier) 272 and isinstance(expression.parent, exp.Table) 273 and expression.name.lower() == "dual" 274 ): 275 return expression # type: ignore 276 277 return super().quote_identifier(expression, identify=identify) 278 279 class Parser(parser.Parser): 280 IDENTIFY_PIVOT_STRINGS = True 281 DEFAULT_SAMPLING_METHOD = "BERNOULLI" 282 COLON_IS_VARIANT_EXTRACT = True 283 284 ID_VAR_TOKENS = { 285 *parser.Parser.ID_VAR_TOKENS, 286 TokenType.MATCH_CONDITION, 287 } 288 289 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 290 TABLE_ALIAS_TOKENS.discard(TokenType.MATCH_CONDITION) 291 292 FUNCTIONS = { 293 **parser.Parser.FUNCTIONS, 294 "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list, 295 "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args), 296 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 297 this=seq_get(args, 1), expression=seq_get(args, 0) 298 ), 299 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 300 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 301 start=seq_get(args, 0), 302 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 303 step=seq_get(args, 2), 304 ), 305 "BITXOR": binary_from_function(exp.BitwiseXor), 306 "BIT_XOR": binary_from_function(exp.BitwiseXor), 307 "BOOLXOR": binary_from_function(exp.Xor), 308 "DATE": _build_datetime("DATE", exp.DataType.Type.DATE), 309 "DATE_TRUNC": _date_trunc_to_time, 310 "DATEADD": _build_date_time_add(exp.DateAdd), 311 "DATEDIFF": _build_datediff, 312 "DIV0": _build_if_from_div0, 313 "FLATTEN": exp.Explode.from_arg_list, 314 "GET_PATH": lambda args, dialect: exp.JSONExtract( 315 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 316 ), 317 "IFF": exp.If.from_arg_list, 318 "LAST_DAY": lambda args: exp.LastDay( 319 this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1)) 320 ), 321 "LEN": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 322 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 323 "LISTAGG": exp.GroupConcat.from_arg_list, 324 "MEDIAN": lambda args: exp.PercentileCont( 325 this=seq_get(args, 0), expression=exp.Literal.number(0.5) 326 ), 327 "NULLIFZERO": _build_if_from_nullifzero, 328 "OBJECT_CONSTRUCT": _build_object_construct, 329 "REGEXP_REPLACE": _build_regexp_replace, 330 "REGEXP_SUBSTR": lambda args: exp.RegexpExtract( 331 this=seq_get(args, 0), 332 expression=seq_get(args, 1), 333 position=seq_get(args, 2), 334 occurrence=seq_get(args, 3), 335 parameters=seq_get(args, 4), 336 group=seq_get(args, 5) or exp.Literal.number(0), 337 ), 338 "RLIKE": exp.RegexpLike.from_arg_list, 339 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 340 "TIMEADD": _build_date_time_add(exp.TimeAdd), 341 "TIMEDIFF": _build_datediff, 342 "TIMESTAMPADD": _build_date_time_add(exp.DateAdd), 343 "TIMESTAMPDIFF": _build_datediff, 344 "TIMESTAMPFROMPARTS": build_timestamp_from_parts, 345 "TIMESTAMP_FROM_PARTS": build_timestamp_from_parts, 346 "TRY_PARSE_JSON": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True), 347 "TRY_TO_DATE": _build_datetime("TRY_TO_DATE", exp.DataType.Type.DATE, safe=True), 348 "TO_DATE": _build_datetime("TO_DATE", exp.DataType.Type.DATE), 349 "TO_NUMBER": lambda args: exp.ToNumber( 350 this=seq_get(args, 0), 351 format=seq_get(args, 1), 352 precision=seq_get(args, 2), 353 scale=seq_get(args, 3), 354 ), 355 "TO_TIME": _build_datetime("TO_TIME", exp.DataType.Type.TIME), 356 "TO_TIMESTAMP": _build_datetime("TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP), 357 "TO_TIMESTAMP_LTZ": _build_datetime("TO_TIMESTAMP_LTZ", exp.DataType.Type.TIMESTAMPLTZ), 358 "TO_TIMESTAMP_NTZ": _build_datetime("TO_TIMESTAMP_NTZ", exp.DataType.Type.TIMESTAMP), 359 "TO_TIMESTAMP_TZ": _build_datetime("TO_TIMESTAMP_TZ", exp.DataType.Type.TIMESTAMPTZ), 360 "TO_VARCHAR": exp.ToChar.from_arg_list, 361 "ZEROIFNULL": _build_if_from_zeroifnull, 362 } 363 364 FUNCTION_PARSERS = { 365 **parser.Parser.FUNCTION_PARSERS, 366 "DATE_PART": lambda self: self._parse_date_part(), 367 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 368 } 369 FUNCTION_PARSERS.pop("TRIM") 370 371 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 372 373 RANGE_PARSERS = { 374 **parser.Parser.RANGE_PARSERS, 375 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 376 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 377 } 378 379 ALTER_PARSERS = { 380 **parser.Parser.ALTER_PARSERS, 381 "UNSET": lambda self: self.expression( 382 exp.Set, 383 tag=self._match_text_seq("TAG"), 384 expressions=self._parse_csv(self._parse_id_var), 385 unset=True, 386 ), 387 "SWAP": lambda self: self._parse_alter_table_swap(), 388 } 389 390 STATEMENT_PARSERS = { 391 **parser.Parser.STATEMENT_PARSERS, 392 TokenType.SHOW: lambda self: self._parse_show(), 393 } 394 395 PROPERTY_PARSERS = { 396 **parser.Parser.PROPERTY_PARSERS, 397 "LOCATION": lambda self: self._parse_location_property(), 398 } 399 400 TYPE_CONVERTERS = { 401 # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number 402 exp.DataType.Type.DECIMAL: build_default_decimal_type(precision=38, scale=0), 403 } 404 405 SHOW_PARSERS = { 406 "SCHEMAS": _show_parser("SCHEMAS"), 407 "TERSE SCHEMAS": _show_parser("SCHEMAS"), 408 "OBJECTS": _show_parser("OBJECTS"), 409 "TERSE OBJECTS": _show_parser("OBJECTS"), 410 "TABLES": _show_parser("TABLES"), 411 "TERSE TABLES": _show_parser("TABLES"), 412 "VIEWS": _show_parser("VIEWS"), 413 "TERSE VIEWS": _show_parser("VIEWS"), 414 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 415 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 416 "IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 417 "TERSE IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 418 "UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 419 "TERSE UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 420 "SEQUENCES": _show_parser("SEQUENCES"), 421 "TERSE SEQUENCES": _show_parser("SEQUENCES"), 422 "COLUMNS": _show_parser("COLUMNS"), 423 "USERS": _show_parser("USERS"), 424 "TERSE USERS": _show_parser("USERS"), 425 } 426 427 CONSTRAINT_PARSERS = { 428 **parser.Parser.CONSTRAINT_PARSERS, 429 "WITH": lambda self: self._parse_with_constraint(), 430 "MASKING": lambda self: self._parse_with_constraint(), 431 "PROJECTION": lambda self: self._parse_with_constraint(), 432 "TAG": lambda self: self._parse_with_constraint(), 433 } 434 435 STAGED_FILE_SINGLE_TOKENS = { 436 TokenType.DOT, 437 TokenType.MOD, 438 TokenType.SLASH, 439 } 440 441 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 442 443 SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"} 444 445 NON_TABLE_CREATABLES = {"STORAGE INTEGRATION", "TAG", "WAREHOUSE", "STREAMLIT"} 446 447 LAMBDAS = { 448 **parser.Parser.LAMBDAS, 449 TokenType.ARROW: lambda self, expressions: self.expression( 450 exp.Lambda, 451 this=self._replace_lambda( 452 self._parse_assignment(), 453 expressions, 454 ), 455 expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions], 456 ), 457 } 458 459 def _negate_range( 460 self, this: t.Optional[exp.Expression] = None 461 ) -> t.Optional[exp.Expression]: 462 if not this: 463 return this 464 465 query = this.args.get("query") 466 if isinstance(this, exp.In) and isinstance(query, exp.Query): 467 # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so 468 # we do this conversion here to avoid parsing it into `NOT value IN (subquery)` 469 # which can produce different results (most likely a SnowFlake bug). 470 # 471 # https://docs.snowflake.com/en/sql-reference/functions/in 472 # Context: https://github.com/tobymao/sqlglot/issues/3890 473 return self.expression( 474 exp.NEQ, this=this.this, expression=exp.All(this=query.unnest()) 475 ) 476 477 return self.expression(exp.Not, this=this) 478 479 def _parse_with_constraint(self) -> t.Optional[exp.Expression]: 480 if self._prev.token_type != TokenType.WITH: 481 self._retreat(self._index - 1) 482 483 if self._match_text_seq("MASKING", "POLICY"): 484 policy = self._parse_column() 485 return self.expression( 486 exp.MaskingPolicyColumnConstraint, 487 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 488 expressions=self._match(TokenType.USING) 489 and self._parse_wrapped_csv(self._parse_id_var), 490 ) 491 if self._match_text_seq("PROJECTION", "POLICY"): 492 policy = self._parse_column() 493 return self.expression( 494 exp.ProjectionPolicyColumnConstraint, 495 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 496 ) 497 if self._match(TokenType.TAG): 498 return self.expression( 499 exp.TagColumnConstraint, 500 expressions=self._parse_wrapped_csv(self._parse_property), 501 ) 502 503 return None 504 505 def _parse_create(self) -> exp.Create | exp.Command: 506 expression = super()._parse_create() 507 if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES: 508 # Replace the Table node with the enclosed Identifier 509 expression.this.replace(expression.this.this) 510 511 return expression 512 513 # https://docs.snowflake.com/en/sql-reference/functions/date_part.html 514 # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 515 def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 516 this = self._parse_var() or self._parse_type() 517 518 if not this: 519 return None 520 521 self._match(TokenType.COMMA) 522 expression = self._parse_bitwise() 523 this = map_date_part(this) 524 name = this.name.upper() 525 526 if name.startswith("EPOCH"): 527 if name == "EPOCH_MILLISECOND": 528 scale = 10**3 529 elif name == "EPOCH_MICROSECOND": 530 scale = 10**6 531 elif name == "EPOCH_NANOSECOND": 532 scale = 10**9 533 else: 534 scale = None 535 536 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 537 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 538 539 if scale: 540 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 541 542 return to_unix 543 544 return self.expression(exp.Extract, this=this, expression=expression) 545 546 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 547 if is_map: 548 # Keys are strings in Snowflake's objects, see also: 549 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 550 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 551 return self._parse_slice(self._parse_string()) 552 553 return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True)) 554 555 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 556 lateral = super()._parse_lateral() 557 if not lateral: 558 return lateral 559 560 if isinstance(lateral.this, exp.Explode): 561 table_alias = lateral.args.get("alias") 562 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 563 if table_alias and not table_alias.args.get("columns"): 564 table_alias.set("columns", columns) 565 elif not table_alias: 566 exp.alias_(lateral, "_flattened", table=columns, copy=False) 567 568 return lateral 569 570 def _parse_table_parts( 571 self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False 572 ) -> exp.Table: 573 # https://docs.snowflake.com/en/user-guide/querying-stage 574 if self._match(TokenType.STRING, advance=False): 575 table = self._parse_string() 576 elif self._match_text_seq("@", advance=False): 577 table = self._parse_location_path() 578 else: 579 table = None 580 581 if table: 582 file_format = None 583 pattern = None 584 585 wrapped = self._match(TokenType.L_PAREN) 586 while self._curr and wrapped and not self._match(TokenType.R_PAREN): 587 if self._match_text_seq("FILE_FORMAT", "=>"): 588 file_format = self._parse_string() or super()._parse_table_parts( 589 is_db_reference=is_db_reference 590 ) 591 elif self._match_text_seq("PATTERN", "=>"): 592 pattern = self._parse_string() 593 else: 594 break 595 596 self._match(TokenType.COMMA) 597 598 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 599 else: 600 table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 601 602 return table 603 604 def _parse_id_var( 605 self, 606 any_token: bool = True, 607 tokens: t.Optional[t.Collection[TokenType]] = None, 608 ) -> t.Optional[exp.Expression]: 609 if self._match_text_seq("IDENTIFIER", "("): 610 identifier = ( 611 super()._parse_id_var(any_token=any_token, tokens=tokens) 612 or self._parse_string() 613 ) 614 self._match_r_paren() 615 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 616 617 return super()._parse_id_var(any_token=any_token, tokens=tokens) 618 619 def _parse_show_snowflake(self, this: str) -> exp.Show: 620 scope = None 621 scope_kind = None 622 623 # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS 624 # which is syntactically valid but has no effect on the output 625 terse = self._tokens[self._index - 2].text.upper() == "TERSE" 626 627 history = self._match_text_seq("HISTORY") 628 629 like = self._parse_string() if self._match(TokenType.LIKE) else None 630 631 if self._match(TokenType.IN): 632 if self._match_text_seq("ACCOUNT"): 633 scope_kind = "ACCOUNT" 634 elif self._match_set(self.DB_CREATABLES): 635 scope_kind = self._prev.text.upper() 636 if self._curr: 637 scope = self._parse_table_parts() 638 elif self._curr: 639 scope_kind = "SCHEMA" if this in self.SCHEMA_KINDS else "TABLE" 640 scope = self._parse_table_parts() 641 642 return self.expression( 643 exp.Show, 644 **{ 645 "terse": terse, 646 "this": this, 647 "history": history, 648 "like": like, 649 "scope": scope, 650 "scope_kind": scope_kind, 651 "starts_with": self._match_text_seq("STARTS", "WITH") and self._parse_string(), 652 "limit": self._parse_limit(), 653 "from": self._parse_string() if self._match(TokenType.FROM) else None, 654 }, 655 ) 656 657 def _parse_alter_table_swap(self) -> exp.SwapTable: 658 self._match_text_seq("WITH") 659 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 660 661 def _parse_location_property(self) -> exp.LocationProperty: 662 self._match(TokenType.EQ) 663 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 664 665 def _parse_file_location(self) -> t.Optional[exp.Expression]: 666 # Parse either a subquery or a staged file 667 return ( 668 self._parse_select(table=True, parse_subquery_alias=False) 669 if self._match(TokenType.L_PAREN, advance=False) 670 else self._parse_table_parts() 671 ) 672 673 def _parse_location_path(self) -> exp.Var: 674 parts = [self._advance_any(ignore_reserved=True)] 675 676 # We avoid consuming a comma token because external tables like @foo and @bar 677 # can be joined in a query with a comma separator, as well as closing paren 678 # in case of subqueries 679 while self._is_connected() and not self._match_set( 680 (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False 681 ): 682 parts.append(self._advance_any(ignore_reserved=True)) 683 684 return exp.var("".join(part.text for part in parts if part)) 685 686 def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: 687 this = super()._parse_lambda_arg() 688 689 if not this: 690 return this 691 692 typ = self._parse_types() 693 694 if typ: 695 return self.expression(exp.Cast, this=this, to=typ) 696 697 return this 698 699 class Tokenizer(tokens.Tokenizer): 700 STRING_ESCAPES = ["\\", "'"] 701 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 702 RAW_STRINGS = ["$$"] 703 COMMENTS = ["--", "//", ("/*", "*/")] 704 NESTED_COMMENTS = False 705 706 KEYWORDS = { 707 **tokens.Tokenizer.KEYWORDS, 708 "BYTEINT": TokenType.INT, 709 "CHAR VARYING": TokenType.VARCHAR, 710 "CHARACTER VARYING": TokenType.VARCHAR, 711 "EXCLUDE": TokenType.EXCEPT, 712 "ILIKE ANY": TokenType.ILIKE_ANY, 713 "LIKE ANY": TokenType.LIKE_ANY, 714 "MATCH_CONDITION": TokenType.MATCH_CONDITION, 715 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 716 "MINUS": TokenType.EXCEPT, 717 "NCHAR VARYING": TokenType.VARCHAR, 718 "PUT": TokenType.COMMAND, 719 "REMOVE": TokenType.COMMAND, 720 "RM": TokenType.COMMAND, 721 "SAMPLE": TokenType.TABLE_SAMPLE, 722 "SQL_DOUBLE": TokenType.DOUBLE, 723 "SQL_VARCHAR": TokenType.VARCHAR, 724 "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION, 725 "TAG": TokenType.TAG, 726 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 727 "TOP": TokenType.TOP, 728 "WAREHOUSE": TokenType.WAREHOUSE, 729 "STREAMLIT": TokenType.STREAMLIT, 730 } 731 KEYWORDS.pop("/*+") 732 733 SINGLE_TOKENS = { 734 **tokens.Tokenizer.SINGLE_TOKENS, 735 "$": TokenType.PARAMETER, 736 } 737 738 VAR_SINGLE_TOKENS = {"$"} 739 740 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 741 742 class Generator(generator.Generator): 743 PARAMETER_TOKEN = "$" 744 MATCHED_BY_SOURCE = False 745 SINGLE_STRING_INTERVAL = True 746 JOIN_HINTS = False 747 TABLE_HINTS = False 748 QUERY_HINTS = False 749 AGGREGATE_FILTER_SUPPORTED = False 750 SUPPORTS_TABLE_COPY = False 751 COLLATE_IS_FUNC = True 752 LIMIT_ONLY_LITERALS = True 753 JSON_KEY_VALUE_PAIR_SEP = "," 754 INSERT_OVERWRITE = " OVERWRITE INTO" 755 STRUCT_DELIMITER = ("(", ")") 756 COPY_PARAMS_ARE_WRAPPED = False 757 COPY_PARAMS_EQ_REQUIRED = True 758 STAR_EXCEPT = "EXCLUDE" 759 SUPPORTS_EXPLODING_PROJECTIONS = False 760 ARRAY_CONCAT_IS_VAR_LEN = False 761 SUPPORTS_CONVERT_TIMEZONE = True 762 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 763 764 TRANSFORMS = { 765 **generator.Generator.TRANSFORMS, 766 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 767 exp.ArgMax: rename_func("MAX_BY"), 768 exp.ArgMin: rename_func("MIN_BY"), 769 exp.Array: inline_array_sql, 770 exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"), 771 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 772 exp.AtTimeZone: lambda self, e: self.func( 773 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 774 ), 775 exp.BitwiseXor: rename_func("BITXOR"), 776 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 777 exp.DateAdd: date_delta_sql("DATEADD"), 778 exp.DateDiff: date_delta_sql("DATEDIFF"), 779 exp.DateStrToDate: datestrtodate_sql, 780 exp.DayOfMonth: rename_func("DAYOFMONTH"), 781 exp.DayOfWeek: rename_func("DAYOFWEEK"), 782 exp.DayOfYear: rename_func("DAYOFYEAR"), 783 exp.Explode: rename_func("FLATTEN"), 784 exp.Extract: rename_func("DATE_PART"), 785 exp.FromTimeZone: lambda self, e: self.func( 786 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 787 ), 788 exp.GenerateSeries: lambda self, e: self.func( 789 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 790 ), 791 exp.GroupConcat: rename_func("LISTAGG"), 792 exp.If: if_sql(name="IFF", false_value="NULL"), 793 exp.JSONExtract: lambda self, e: self.func("GET_PATH", e.this, e.expression), 794 exp.JSONExtractScalar: lambda self, e: self.func( 795 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 796 ), 797 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 798 exp.JSONPathRoot: lambda *_: "", 799 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 800 exp.LogicalOr: rename_func("BOOLOR_AGG"), 801 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 802 exp.Max: max_or_greatest, 803 exp.Min: min_or_least, 804 exp.ParseJSON: lambda self, e: self.func( 805 "TRY_PARSE_JSON" if e.args.get("safe") else "PARSE_JSON", e.this 806 ), 807 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 808 exp.PercentileCont: transforms.preprocess( 809 [transforms.add_within_group_for_percentiles] 810 ), 811 exp.PercentileDisc: transforms.preprocess( 812 [transforms.add_within_group_for_percentiles] 813 ), 814 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 815 exp.RegexpILike: _regexpilike_sql, 816 exp.Rand: rename_func("RANDOM"), 817 exp.Select: transforms.preprocess( 818 [ 819 transforms.eliminate_distinct_on, 820 transforms.explode_to_unnest(), 821 transforms.eliminate_semi_and_anti_joins, 822 _unnest_generate_date_array, 823 ] 824 ), 825 exp.SHA: rename_func("SHA1"), 826 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 827 exp.StartsWith: rename_func("STARTSWITH"), 828 exp.StrPosition: lambda self, e: self.func( 829 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 830 ), 831 exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)), 832 exp.Stuff: rename_func("INSERT"), 833 exp.TimeAdd: date_delta_sql("TIMEADD"), 834 exp.TimestampDiff: lambda self, e: self.func( 835 "TIMESTAMPDIFF", e.unit, e.expression, e.this 836 ), 837 exp.TimestampTrunc: timestamptrunc_sql(), 838 exp.TimeStrToTime: timestrtotime_sql, 839 exp.TimeToStr: lambda self, e: self.func( 840 "TO_CHAR", exp.cast(e.this, exp.DataType.Type.TIMESTAMP), self.format_time(e) 841 ), 842 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 843 exp.ToArray: rename_func("TO_ARRAY"), 844 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 845 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 846 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 847 exp.TsOrDsToDate: lambda self, e: self.func( 848 "TRY_TO_DATE" if e.args.get("safe") else "TO_DATE", e.this, self.format_time(e) 849 ), 850 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 851 exp.Uuid: rename_func("UUID_STRING"), 852 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 853 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 854 exp.Xor: rename_func("BOOLXOR"), 855 } 856 857 SUPPORTED_JSON_PATH_PARTS = { 858 exp.JSONPathKey, 859 exp.JSONPathRoot, 860 exp.JSONPathSubscript, 861 } 862 863 TYPE_MAPPING = { 864 **generator.Generator.TYPE_MAPPING, 865 exp.DataType.Type.NESTED: "OBJECT", 866 exp.DataType.Type.STRUCT: "OBJECT", 867 } 868 869 PROPERTIES_LOCATION = { 870 **generator.Generator.PROPERTIES_LOCATION, 871 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 872 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 873 } 874 875 UNSUPPORTED_VALUES_EXPRESSIONS = { 876 exp.Map, 877 exp.StarMap, 878 exp.Struct, 879 exp.VarMap, 880 } 881 882 def with_properties(self, properties: exp.Properties) -> str: 883 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 884 885 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 886 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 887 values_as_table = False 888 889 return super().values_sql(expression, values_as_table=values_as_table) 890 891 def datatype_sql(self, expression: exp.DataType) -> str: 892 expressions = expression.expressions 893 if ( 894 expressions 895 and expression.is_type(*exp.DataType.STRUCT_TYPES) 896 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 897 ): 898 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 899 return "OBJECT" 900 901 return super().datatype_sql(expression) 902 903 def tonumber_sql(self, expression: exp.ToNumber) -> str: 904 return self.func( 905 "TO_NUMBER", 906 expression.this, 907 expression.args.get("format"), 908 expression.args.get("precision"), 909 expression.args.get("scale"), 910 ) 911 912 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 913 milli = expression.args.get("milli") 914 if milli is not None: 915 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 916 expression.set("nano", milli_to_nano) 917 918 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 919 920 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 921 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 922 return self.func("TO_GEOGRAPHY", expression.this) 923 if expression.is_type(exp.DataType.Type.GEOMETRY): 924 return self.func("TO_GEOMETRY", expression.this) 925 926 return super().cast_sql(expression, safe_prefix=safe_prefix) 927 928 def trycast_sql(self, expression: exp.TryCast) -> str: 929 value = expression.this 930 931 if value.type is None: 932 from sqlglot.optimizer.annotate_types import annotate_types 933 934 value = annotate_types(value) 935 936 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 937 return super().trycast_sql(expression) 938 939 # TRY_CAST only works for string values in Snowflake 940 return self.cast_sql(expression) 941 942 def log_sql(self, expression: exp.Log) -> str: 943 if not expression.expression: 944 return self.func("LN", expression.this) 945 946 return super().log_sql(expression) 947 948 def unnest_sql(self, expression: exp.Unnest) -> str: 949 unnest_alias = expression.args.get("alias") 950 offset = expression.args.get("offset") 951 952 columns = [ 953 exp.to_identifier("seq"), 954 exp.to_identifier("key"), 955 exp.to_identifier("path"), 956 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 957 seq_get(unnest_alias.columns if unnest_alias else [], 0) 958 or exp.to_identifier("value"), 959 exp.to_identifier("this"), 960 ] 961 962 if unnest_alias: 963 unnest_alias.set("columns", columns) 964 else: 965 unnest_alias = exp.TableAlias(this="_u", columns=columns) 966 967 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 968 alias = self.sql(unnest_alias) 969 alias = f" AS {alias}" if alias else "" 970 return f"{explode}{alias}" 971 972 def show_sql(self, expression: exp.Show) -> str: 973 terse = "TERSE " if expression.args.get("terse") else "" 974 history = " HISTORY" if expression.args.get("history") else "" 975 like = self.sql(expression, "like") 976 like = f" LIKE {like}" if like else "" 977 978 scope = self.sql(expression, "scope") 979 scope = f" {scope}" if scope else "" 980 981 scope_kind = self.sql(expression, "scope_kind") 982 if scope_kind: 983 scope_kind = f" IN {scope_kind}" 984 985 starts_with = self.sql(expression, "starts_with") 986 if starts_with: 987 starts_with = f" STARTS WITH {starts_with}" 988 989 limit = self.sql(expression, "limit") 990 991 from_ = self.sql(expression, "from") 992 if from_: 993 from_ = f" FROM {from_}" 994 995 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}" 996 997 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 998 # Other dialects don't support all of the following parameters, so we need to 999 # generate default values as necessary to ensure the transpilation is correct 1000 group = expression.args.get("group") 1001 1002 # To avoid generating all these default values, we set group to None if 1003 # it's 0 (also default value) which doesn't trigger the following chain 1004 if group and group.name == "0": 1005 group = None 1006 1007 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 1008 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 1009 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 1010 1011 return self.func( 1012 "REGEXP_SUBSTR", 1013 expression.this, 1014 expression.expression, 1015 position, 1016 occurrence, 1017 parameters, 1018 group, 1019 ) 1020 1021 def describe_sql(self, expression: exp.Describe) -> str: 1022 # Default to table if kind is unknown 1023 kind_value = expression.args.get("kind") or "TABLE" 1024 kind = f" {kind_value}" if kind_value else "" 1025 this = f" {self.sql(expression, 'this')}" 1026 expressions = self.expressions(expression, flat=True) 1027 expressions = f" {expressions}" if expressions else "" 1028 return f"DESCRIBE{kind}{this}{expressions}" 1029 1030 def generatedasidentitycolumnconstraint_sql( 1031 self, expression: exp.GeneratedAsIdentityColumnConstraint 1032 ) -> str: 1033 start = expression.args.get("start") 1034 start = f" START {start}" if start else "" 1035 increment = expression.args.get("increment") 1036 increment = f" INCREMENT {increment}" if increment else "" 1037 return f"AUTOINCREMENT{start}{increment}" 1038 1039 def swaptable_sql(self, expression: exp.SwapTable) -> str: 1040 this = self.sql(expression, "this") 1041 return f"SWAP WITH {this}" 1042 1043 def cluster_sql(self, expression: exp.Cluster) -> str: 1044 return f"CLUSTER BY ({self.expressions(expression, flat=True)})" 1045 1046 def struct_sql(self, expression: exp.Struct) -> str: 1047 keys = [] 1048 values = [] 1049 1050 for i, e in enumerate(expression.expressions): 1051 if isinstance(e, exp.PropertyEQ): 1052 keys.append( 1053 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1054 ) 1055 values.append(e.expression) 1056 else: 1057 keys.append(exp.Literal.string(f"_{i}")) 1058 values.append(e) 1059 1060 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 1061 1062 @generator.unsupported_args("weight", "accuracy") 1063 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 1064 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 1065 1066 def alterset_sql(self, expression: exp.AlterSet) -> str: 1067 exprs = self.expressions(expression, flat=True) 1068 exprs = f" {exprs}" if exprs else "" 1069 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1070 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1071 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1072 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1073 tag = self.expressions(expression, key="tag", flat=True) 1074 tag = f" TAG {tag}" if tag else "" 1075 1076 return f"SET{exprs}{file_format}{copy_options}{tag}"
227class Snowflake(Dialect): 228 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 229 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 230 NULL_ORDERING = "nulls_are_large" 231 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 232 SUPPORTS_USER_DEFINED_TYPES = False 233 SUPPORTS_SEMI_ANTI_JOIN = False 234 PREFER_CTE_ALIAS_COLUMN = True 235 TABLESAMPLE_SIZE_IS_PERCENT = True 236 COPY_PARAMS_ARE_CSV = False 237 ARRAY_AGG_INCLUDES_NULLS = None 238 239 TIME_MAPPING = { 240 "YYYY": "%Y", 241 "yyyy": "%Y", 242 "YY": "%y", 243 "yy": "%y", 244 "MMMM": "%B", 245 "mmmm": "%B", 246 "MON": "%b", 247 "mon": "%b", 248 "MM": "%m", 249 "mm": "%m", 250 "DD": "%d", 251 "dd": "%-d", 252 "DY": "%a", 253 "dy": "%w", 254 "HH24": "%H", 255 "hh24": "%H", 256 "HH12": "%I", 257 "hh12": "%I", 258 "MI": "%M", 259 "mi": "%M", 260 "SS": "%S", 261 "ss": "%S", 262 "FF": "%f", 263 "ff": "%f", 264 "FF6": "%f", 265 "ff6": "%f", 266 } 267 268 def quote_identifier(self, expression: E, identify: bool = True) -> E: 269 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 270 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 271 if ( 272 isinstance(expression, exp.Identifier) 273 and isinstance(expression.parent, exp.Table) 274 and expression.name.lower() == "dual" 275 ): 276 return expression # type: ignore 277 278 return super().quote_identifier(expression, identify=identify) 279 280 class Parser(parser.Parser): 281 IDENTIFY_PIVOT_STRINGS = True 282 DEFAULT_SAMPLING_METHOD = "BERNOULLI" 283 COLON_IS_VARIANT_EXTRACT = True 284 285 ID_VAR_TOKENS = { 286 *parser.Parser.ID_VAR_TOKENS, 287 TokenType.MATCH_CONDITION, 288 } 289 290 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 291 TABLE_ALIAS_TOKENS.discard(TokenType.MATCH_CONDITION) 292 293 FUNCTIONS = { 294 **parser.Parser.FUNCTIONS, 295 "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list, 296 "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args), 297 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 298 this=seq_get(args, 1), expression=seq_get(args, 0) 299 ), 300 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 301 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 302 start=seq_get(args, 0), 303 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 304 step=seq_get(args, 2), 305 ), 306 "BITXOR": binary_from_function(exp.BitwiseXor), 307 "BIT_XOR": binary_from_function(exp.BitwiseXor), 308 "BOOLXOR": binary_from_function(exp.Xor), 309 "DATE": _build_datetime("DATE", exp.DataType.Type.DATE), 310 "DATE_TRUNC": _date_trunc_to_time, 311 "DATEADD": _build_date_time_add(exp.DateAdd), 312 "DATEDIFF": _build_datediff, 313 "DIV0": _build_if_from_div0, 314 "FLATTEN": exp.Explode.from_arg_list, 315 "GET_PATH": lambda args, dialect: exp.JSONExtract( 316 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 317 ), 318 "IFF": exp.If.from_arg_list, 319 "LAST_DAY": lambda args: exp.LastDay( 320 this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1)) 321 ), 322 "LEN": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 323 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 324 "LISTAGG": exp.GroupConcat.from_arg_list, 325 "MEDIAN": lambda args: exp.PercentileCont( 326 this=seq_get(args, 0), expression=exp.Literal.number(0.5) 327 ), 328 "NULLIFZERO": _build_if_from_nullifzero, 329 "OBJECT_CONSTRUCT": _build_object_construct, 330 "REGEXP_REPLACE": _build_regexp_replace, 331 "REGEXP_SUBSTR": lambda args: exp.RegexpExtract( 332 this=seq_get(args, 0), 333 expression=seq_get(args, 1), 334 position=seq_get(args, 2), 335 occurrence=seq_get(args, 3), 336 parameters=seq_get(args, 4), 337 group=seq_get(args, 5) or exp.Literal.number(0), 338 ), 339 "RLIKE": exp.RegexpLike.from_arg_list, 340 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 341 "TIMEADD": _build_date_time_add(exp.TimeAdd), 342 "TIMEDIFF": _build_datediff, 343 "TIMESTAMPADD": _build_date_time_add(exp.DateAdd), 344 "TIMESTAMPDIFF": _build_datediff, 345 "TIMESTAMPFROMPARTS": build_timestamp_from_parts, 346 "TIMESTAMP_FROM_PARTS": build_timestamp_from_parts, 347 "TRY_PARSE_JSON": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True), 348 "TRY_TO_DATE": _build_datetime("TRY_TO_DATE", exp.DataType.Type.DATE, safe=True), 349 "TO_DATE": _build_datetime("TO_DATE", exp.DataType.Type.DATE), 350 "TO_NUMBER": lambda args: exp.ToNumber( 351 this=seq_get(args, 0), 352 format=seq_get(args, 1), 353 precision=seq_get(args, 2), 354 scale=seq_get(args, 3), 355 ), 356 "TO_TIME": _build_datetime("TO_TIME", exp.DataType.Type.TIME), 357 "TO_TIMESTAMP": _build_datetime("TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP), 358 "TO_TIMESTAMP_LTZ": _build_datetime("TO_TIMESTAMP_LTZ", exp.DataType.Type.TIMESTAMPLTZ), 359 "TO_TIMESTAMP_NTZ": _build_datetime("TO_TIMESTAMP_NTZ", exp.DataType.Type.TIMESTAMP), 360 "TO_TIMESTAMP_TZ": _build_datetime("TO_TIMESTAMP_TZ", exp.DataType.Type.TIMESTAMPTZ), 361 "TO_VARCHAR": exp.ToChar.from_arg_list, 362 "ZEROIFNULL": _build_if_from_zeroifnull, 363 } 364 365 FUNCTION_PARSERS = { 366 **parser.Parser.FUNCTION_PARSERS, 367 "DATE_PART": lambda self: self._parse_date_part(), 368 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 369 } 370 FUNCTION_PARSERS.pop("TRIM") 371 372 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 373 374 RANGE_PARSERS = { 375 **parser.Parser.RANGE_PARSERS, 376 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 377 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 378 } 379 380 ALTER_PARSERS = { 381 **parser.Parser.ALTER_PARSERS, 382 "UNSET": lambda self: self.expression( 383 exp.Set, 384 tag=self._match_text_seq("TAG"), 385 expressions=self._parse_csv(self._parse_id_var), 386 unset=True, 387 ), 388 "SWAP": lambda self: self._parse_alter_table_swap(), 389 } 390 391 STATEMENT_PARSERS = { 392 **parser.Parser.STATEMENT_PARSERS, 393 TokenType.SHOW: lambda self: self._parse_show(), 394 } 395 396 PROPERTY_PARSERS = { 397 **parser.Parser.PROPERTY_PARSERS, 398 "LOCATION": lambda self: self._parse_location_property(), 399 } 400 401 TYPE_CONVERTERS = { 402 # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number 403 exp.DataType.Type.DECIMAL: build_default_decimal_type(precision=38, scale=0), 404 } 405 406 SHOW_PARSERS = { 407 "SCHEMAS": _show_parser("SCHEMAS"), 408 "TERSE SCHEMAS": _show_parser("SCHEMAS"), 409 "OBJECTS": _show_parser("OBJECTS"), 410 "TERSE OBJECTS": _show_parser("OBJECTS"), 411 "TABLES": _show_parser("TABLES"), 412 "TERSE TABLES": _show_parser("TABLES"), 413 "VIEWS": _show_parser("VIEWS"), 414 "TERSE VIEWS": _show_parser("VIEWS"), 415 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 416 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 417 "IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 418 "TERSE IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 419 "UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 420 "TERSE UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 421 "SEQUENCES": _show_parser("SEQUENCES"), 422 "TERSE SEQUENCES": _show_parser("SEQUENCES"), 423 "COLUMNS": _show_parser("COLUMNS"), 424 "USERS": _show_parser("USERS"), 425 "TERSE USERS": _show_parser("USERS"), 426 } 427 428 CONSTRAINT_PARSERS = { 429 **parser.Parser.CONSTRAINT_PARSERS, 430 "WITH": lambda self: self._parse_with_constraint(), 431 "MASKING": lambda self: self._parse_with_constraint(), 432 "PROJECTION": lambda self: self._parse_with_constraint(), 433 "TAG": lambda self: self._parse_with_constraint(), 434 } 435 436 STAGED_FILE_SINGLE_TOKENS = { 437 TokenType.DOT, 438 TokenType.MOD, 439 TokenType.SLASH, 440 } 441 442 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 443 444 SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"} 445 446 NON_TABLE_CREATABLES = {"STORAGE INTEGRATION", "TAG", "WAREHOUSE", "STREAMLIT"} 447 448 LAMBDAS = { 449 **parser.Parser.LAMBDAS, 450 TokenType.ARROW: lambda self, expressions: self.expression( 451 exp.Lambda, 452 this=self._replace_lambda( 453 self._parse_assignment(), 454 expressions, 455 ), 456 expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions], 457 ), 458 } 459 460 def _negate_range( 461 self, this: t.Optional[exp.Expression] = None 462 ) -> t.Optional[exp.Expression]: 463 if not this: 464 return this 465 466 query = this.args.get("query") 467 if isinstance(this, exp.In) and isinstance(query, exp.Query): 468 # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so 469 # we do this conversion here to avoid parsing it into `NOT value IN (subquery)` 470 # which can produce different results (most likely a SnowFlake bug). 471 # 472 # https://docs.snowflake.com/en/sql-reference/functions/in 473 # Context: https://github.com/tobymao/sqlglot/issues/3890 474 return self.expression( 475 exp.NEQ, this=this.this, expression=exp.All(this=query.unnest()) 476 ) 477 478 return self.expression(exp.Not, this=this) 479 480 def _parse_with_constraint(self) -> t.Optional[exp.Expression]: 481 if self._prev.token_type != TokenType.WITH: 482 self._retreat(self._index - 1) 483 484 if self._match_text_seq("MASKING", "POLICY"): 485 policy = self._parse_column() 486 return self.expression( 487 exp.MaskingPolicyColumnConstraint, 488 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 489 expressions=self._match(TokenType.USING) 490 and self._parse_wrapped_csv(self._parse_id_var), 491 ) 492 if self._match_text_seq("PROJECTION", "POLICY"): 493 policy = self._parse_column() 494 return self.expression( 495 exp.ProjectionPolicyColumnConstraint, 496 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 497 ) 498 if self._match(TokenType.TAG): 499 return self.expression( 500 exp.TagColumnConstraint, 501 expressions=self._parse_wrapped_csv(self._parse_property), 502 ) 503 504 return None 505 506 def _parse_create(self) -> exp.Create | exp.Command: 507 expression = super()._parse_create() 508 if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES: 509 # Replace the Table node with the enclosed Identifier 510 expression.this.replace(expression.this.this) 511 512 return expression 513 514 # https://docs.snowflake.com/en/sql-reference/functions/date_part.html 515 # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 516 def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 517 this = self._parse_var() or self._parse_type() 518 519 if not this: 520 return None 521 522 self._match(TokenType.COMMA) 523 expression = self._parse_bitwise() 524 this = map_date_part(this) 525 name = this.name.upper() 526 527 if name.startswith("EPOCH"): 528 if name == "EPOCH_MILLISECOND": 529 scale = 10**3 530 elif name == "EPOCH_MICROSECOND": 531 scale = 10**6 532 elif name == "EPOCH_NANOSECOND": 533 scale = 10**9 534 else: 535 scale = None 536 537 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 538 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 539 540 if scale: 541 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 542 543 return to_unix 544 545 return self.expression(exp.Extract, this=this, expression=expression) 546 547 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 548 if is_map: 549 # Keys are strings in Snowflake's objects, see also: 550 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 551 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 552 return self._parse_slice(self._parse_string()) 553 554 return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True)) 555 556 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 557 lateral = super()._parse_lateral() 558 if not lateral: 559 return lateral 560 561 if isinstance(lateral.this, exp.Explode): 562 table_alias = lateral.args.get("alias") 563 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 564 if table_alias and not table_alias.args.get("columns"): 565 table_alias.set("columns", columns) 566 elif not table_alias: 567 exp.alias_(lateral, "_flattened", table=columns, copy=False) 568 569 return lateral 570 571 def _parse_table_parts( 572 self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False 573 ) -> exp.Table: 574 # https://docs.snowflake.com/en/user-guide/querying-stage 575 if self._match(TokenType.STRING, advance=False): 576 table = self._parse_string() 577 elif self._match_text_seq("@", advance=False): 578 table = self._parse_location_path() 579 else: 580 table = None 581 582 if table: 583 file_format = None 584 pattern = None 585 586 wrapped = self._match(TokenType.L_PAREN) 587 while self._curr and wrapped and not self._match(TokenType.R_PAREN): 588 if self._match_text_seq("FILE_FORMAT", "=>"): 589 file_format = self._parse_string() or super()._parse_table_parts( 590 is_db_reference=is_db_reference 591 ) 592 elif self._match_text_seq("PATTERN", "=>"): 593 pattern = self._parse_string() 594 else: 595 break 596 597 self._match(TokenType.COMMA) 598 599 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 600 else: 601 table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 602 603 return table 604 605 def _parse_id_var( 606 self, 607 any_token: bool = True, 608 tokens: t.Optional[t.Collection[TokenType]] = None, 609 ) -> t.Optional[exp.Expression]: 610 if self._match_text_seq("IDENTIFIER", "("): 611 identifier = ( 612 super()._parse_id_var(any_token=any_token, tokens=tokens) 613 or self._parse_string() 614 ) 615 self._match_r_paren() 616 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 617 618 return super()._parse_id_var(any_token=any_token, tokens=tokens) 619 620 def _parse_show_snowflake(self, this: str) -> exp.Show: 621 scope = None 622 scope_kind = None 623 624 # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS 625 # which is syntactically valid but has no effect on the output 626 terse = self._tokens[self._index - 2].text.upper() == "TERSE" 627 628 history = self._match_text_seq("HISTORY") 629 630 like = self._parse_string() if self._match(TokenType.LIKE) else None 631 632 if self._match(TokenType.IN): 633 if self._match_text_seq("ACCOUNT"): 634 scope_kind = "ACCOUNT" 635 elif self._match_set(self.DB_CREATABLES): 636 scope_kind = self._prev.text.upper() 637 if self._curr: 638 scope = self._parse_table_parts() 639 elif self._curr: 640 scope_kind = "SCHEMA" if this in self.SCHEMA_KINDS else "TABLE" 641 scope = self._parse_table_parts() 642 643 return self.expression( 644 exp.Show, 645 **{ 646 "terse": terse, 647 "this": this, 648 "history": history, 649 "like": like, 650 "scope": scope, 651 "scope_kind": scope_kind, 652 "starts_with": self._match_text_seq("STARTS", "WITH") and self._parse_string(), 653 "limit": self._parse_limit(), 654 "from": self._parse_string() if self._match(TokenType.FROM) else None, 655 }, 656 ) 657 658 def _parse_alter_table_swap(self) -> exp.SwapTable: 659 self._match_text_seq("WITH") 660 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 661 662 def _parse_location_property(self) -> exp.LocationProperty: 663 self._match(TokenType.EQ) 664 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 665 666 def _parse_file_location(self) -> t.Optional[exp.Expression]: 667 # Parse either a subquery or a staged file 668 return ( 669 self._parse_select(table=True, parse_subquery_alias=False) 670 if self._match(TokenType.L_PAREN, advance=False) 671 else self._parse_table_parts() 672 ) 673 674 def _parse_location_path(self) -> exp.Var: 675 parts = [self._advance_any(ignore_reserved=True)] 676 677 # We avoid consuming a comma token because external tables like @foo and @bar 678 # can be joined in a query with a comma separator, as well as closing paren 679 # in case of subqueries 680 while self._is_connected() and not self._match_set( 681 (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False 682 ): 683 parts.append(self._advance_any(ignore_reserved=True)) 684 685 return exp.var("".join(part.text for part in parts if part)) 686 687 def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: 688 this = super()._parse_lambda_arg() 689 690 if not this: 691 return this 692 693 typ = self._parse_types() 694 695 if typ: 696 return self.expression(exp.Cast, this=this, to=typ) 697 698 return this 699 700 class Tokenizer(tokens.Tokenizer): 701 STRING_ESCAPES = ["\\", "'"] 702 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 703 RAW_STRINGS = ["$$"] 704 COMMENTS = ["--", "//", ("/*", "*/")] 705 NESTED_COMMENTS = False 706 707 KEYWORDS = { 708 **tokens.Tokenizer.KEYWORDS, 709 "BYTEINT": TokenType.INT, 710 "CHAR VARYING": TokenType.VARCHAR, 711 "CHARACTER VARYING": TokenType.VARCHAR, 712 "EXCLUDE": TokenType.EXCEPT, 713 "ILIKE ANY": TokenType.ILIKE_ANY, 714 "LIKE ANY": TokenType.LIKE_ANY, 715 "MATCH_CONDITION": TokenType.MATCH_CONDITION, 716 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 717 "MINUS": TokenType.EXCEPT, 718 "NCHAR VARYING": TokenType.VARCHAR, 719 "PUT": TokenType.COMMAND, 720 "REMOVE": TokenType.COMMAND, 721 "RM": TokenType.COMMAND, 722 "SAMPLE": TokenType.TABLE_SAMPLE, 723 "SQL_DOUBLE": TokenType.DOUBLE, 724 "SQL_VARCHAR": TokenType.VARCHAR, 725 "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION, 726 "TAG": TokenType.TAG, 727 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 728 "TOP": TokenType.TOP, 729 "WAREHOUSE": TokenType.WAREHOUSE, 730 "STREAMLIT": TokenType.STREAMLIT, 731 } 732 KEYWORDS.pop("/*+") 733 734 SINGLE_TOKENS = { 735 **tokens.Tokenizer.SINGLE_TOKENS, 736 "$": TokenType.PARAMETER, 737 } 738 739 VAR_SINGLE_TOKENS = {"$"} 740 741 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 742 743 class Generator(generator.Generator): 744 PARAMETER_TOKEN = "$" 745 MATCHED_BY_SOURCE = False 746 SINGLE_STRING_INTERVAL = True 747 JOIN_HINTS = False 748 TABLE_HINTS = False 749 QUERY_HINTS = False 750 AGGREGATE_FILTER_SUPPORTED = False 751 SUPPORTS_TABLE_COPY = False 752 COLLATE_IS_FUNC = True 753 LIMIT_ONLY_LITERALS = True 754 JSON_KEY_VALUE_PAIR_SEP = "," 755 INSERT_OVERWRITE = " OVERWRITE INTO" 756 STRUCT_DELIMITER = ("(", ")") 757 COPY_PARAMS_ARE_WRAPPED = False 758 COPY_PARAMS_EQ_REQUIRED = True 759 STAR_EXCEPT = "EXCLUDE" 760 SUPPORTS_EXPLODING_PROJECTIONS = False 761 ARRAY_CONCAT_IS_VAR_LEN = False 762 SUPPORTS_CONVERT_TIMEZONE = True 763 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 764 765 TRANSFORMS = { 766 **generator.Generator.TRANSFORMS, 767 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 768 exp.ArgMax: rename_func("MAX_BY"), 769 exp.ArgMin: rename_func("MIN_BY"), 770 exp.Array: inline_array_sql, 771 exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"), 772 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 773 exp.AtTimeZone: lambda self, e: self.func( 774 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 775 ), 776 exp.BitwiseXor: rename_func("BITXOR"), 777 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 778 exp.DateAdd: date_delta_sql("DATEADD"), 779 exp.DateDiff: date_delta_sql("DATEDIFF"), 780 exp.DateStrToDate: datestrtodate_sql, 781 exp.DayOfMonth: rename_func("DAYOFMONTH"), 782 exp.DayOfWeek: rename_func("DAYOFWEEK"), 783 exp.DayOfYear: rename_func("DAYOFYEAR"), 784 exp.Explode: rename_func("FLATTEN"), 785 exp.Extract: rename_func("DATE_PART"), 786 exp.FromTimeZone: lambda self, e: self.func( 787 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 788 ), 789 exp.GenerateSeries: lambda self, e: self.func( 790 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 791 ), 792 exp.GroupConcat: rename_func("LISTAGG"), 793 exp.If: if_sql(name="IFF", false_value="NULL"), 794 exp.JSONExtract: lambda self, e: self.func("GET_PATH", e.this, e.expression), 795 exp.JSONExtractScalar: lambda self, e: self.func( 796 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 797 ), 798 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 799 exp.JSONPathRoot: lambda *_: "", 800 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 801 exp.LogicalOr: rename_func("BOOLOR_AGG"), 802 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 803 exp.Max: max_or_greatest, 804 exp.Min: min_or_least, 805 exp.ParseJSON: lambda self, e: self.func( 806 "TRY_PARSE_JSON" if e.args.get("safe") else "PARSE_JSON", e.this 807 ), 808 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 809 exp.PercentileCont: transforms.preprocess( 810 [transforms.add_within_group_for_percentiles] 811 ), 812 exp.PercentileDisc: transforms.preprocess( 813 [transforms.add_within_group_for_percentiles] 814 ), 815 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 816 exp.RegexpILike: _regexpilike_sql, 817 exp.Rand: rename_func("RANDOM"), 818 exp.Select: transforms.preprocess( 819 [ 820 transforms.eliminate_distinct_on, 821 transforms.explode_to_unnest(), 822 transforms.eliminate_semi_and_anti_joins, 823 _unnest_generate_date_array, 824 ] 825 ), 826 exp.SHA: rename_func("SHA1"), 827 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 828 exp.StartsWith: rename_func("STARTSWITH"), 829 exp.StrPosition: lambda self, e: self.func( 830 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 831 ), 832 exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)), 833 exp.Stuff: rename_func("INSERT"), 834 exp.TimeAdd: date_delta_sql("TIMEADD"), 835 exp.TimestampDiff: lambda self, e: self.func( 836 "TIMESTAMPDIFF", e.unit, e.expression, e.this 837 ), 838 exp.TimestampTrunc: timestamptrunc_sql(), 839 exp.TimeStrToTime: timestrtotime_sql, 840 exp.TimeToStr: lambda self, e: self.func( 841 "TO_CHAR", exp.cast(e.this, exp.DataType.Type.TIMESTAMP), self.format_time(e) 842 ), 843 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 844 exp.ToArray: rename_func("TO_ARRAY"), 845 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 846 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 847 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 848 exp.TsOrDsToDate: lambda self, e: self.func( 849 "TRY_TO_DATE" if e.args.get("safe") else "TO_DATE", e.this, self.format_time(e) 850 ), 851 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 852 exp.Uuid: rename_func("UUID_STRING"), 853 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 854 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 855 exp.Xor: rename_func("BOOLXOR"), 856 } 857 858 SUPPORTED_JSON_PATH_PARTS = { 859 exp.JSONPathKey, 860 exp.JSONPathRoot, 861 exp.JSONPathSubscript, 862 } 863 864 TYPE_MAPPING = { 865 **generator.Generator.TYPE_MAPPING, 866 exp.DataType.Type.NESTED: "OBJECT", 867 exp.DataType.Type.STRUCT: "OBJECT", 868 } 869 870 PROPERTIES_LOCATION = { 871 **generator.Generator.PROPERTIES_LOCATION, 872 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 873 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 874 } 875 876 UNSUPPORTED_VALUES_EXPRESSIONS = { 877 exp.Map, 878 exp.StarMap, 879 exp.Struct, 880 exp.VarMap, 881 } 882 883 def with_properties(self, properties: exp.Properties) -> str: 884 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 885 886 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 887 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 888 values_as_table = False 889 890 return super().values_sql(expression, values_as_table=values_as_table) 891 892 def datatype_sql(self, expression: exp.DataType) -> str: 893 expressions = expression.expressions 894 if ( 895 expressions 896 and expression.is_type(*exp.DataType.STRUCT_TYPES) 897 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 898 ): 899 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 900 return "OBJECT" 901 902 return super().datatype_sql(expression) 903 904 def tonumber_sql(self, expression: exp.ToNumber) -> str: 905 return self.func( 906 "TO_NUMBER", 907 expression.this, 908 expression.args.get("format"), 909 expression.args.get("precision"), 910 expression.args.get("scale"), 911 ) 912 913 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 914 milli = expression.args.get("milli") 915 if milli is not None: 916 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 917 expression.set("nano", milli_to_nano) 918 919 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 920 921 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 922 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 923 return self.func("TO_GEOGRAPHY", expression.this) 924 if expression.is_type(exp.DataType.Type.GEOMETRY): 925 return self.func("TO_GEOMETRY", expression.this) 926 927 return super().cast_sql(expression, safe_prefix=safe_prefix) 928 929 def trycast_sql(self, expression: exp.TryCast) -> str: 930 value = expression.this 931 932 if value.type is None: 933 from sqlglot.optimizer.annotate_types import annotate_types 934 935 value = annotate_types(value) 936 937 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 938 return super().trycast_sql(expression) 939 940 # TRY_CAST only works for string values in Snowflake 941 return self.cast_sql(expression) 942 943 def log_sql(self, expression: exp.Log) -> str: 944 if not expression.expression: 945 return self.func("LN", expression.this) 946 947 return super().log_sql(expression) 948 949 def unnest_sql(self, expression: exp.Unnest) -> str: 950 unnest_alias = expression.args.get("alias") 951 offset = expression.args.get("offset") 952 953 columns = [ 954 exp.to_identifier("seq"), 955 exp.to_identifier("key"), 956 exp.to_identifier("path"), 957 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 958 seq_get(unnest_alias.columns if unnest_alias else [], 0) 959 or exp.to_identifier("value"), 960 exp.to_identifier("this"), 961 ] 962 963 if unnest_alias: 964 unnest_alias.set("columns", columns) 965 else: 966 unnest_alias = exp.TableAlias(this="_u", columns=columns) 967 968 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 969 alias = self.sql(unnest_alias) 970 alias = f" AS {alias}" if alias else "" 971 return f"{explode}{alias}" 972 973 def show_sql(self, expression: exp.Show) -> str: 974 terse = "TERSE " if expression.args.get("terse") else "" 975 history = " HISTORY" if expression.args.get("history") else "" 976 like = self.sql(expression, "like") 977 like = f" LIKE {like}" if like else "" 978 979 scope = self.sql(expression, "scope") 980 scope = f" {scope}" if scope else "" 981 982 scope_kind = self.sql(expression, "scope_kind") 983 if scope_kind: 984 scope_kind = f" IN {scope_kind}" 985 986 starts_with = self.sql(expression, "starts_with") 987 if starts_with: 988 starts_with = f" STARTS WITH {starts_with}" 989 990 limit = self.sql(expression, "limit") 991 992 from_ = self.sql(expression, "from") 993 if from_: 994 from_ = f" FROM {from_}" 995 996 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}" 997 998 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 999 # Other dialects don't support all of the following parameters, so we need to 1000 # generate default values as necessary to ensure the transpilation is correct 1001 group = expression.args.get("group") 1002 1003 # To avoid generating all these default values, we set group to None if 1004 # it's 0 (also default value) which doesn't trigger the following chain 1005 if group and group.name == "0": 1006 group = None 1007 1008 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 1009 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 1010 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 1011 1012 return self.func( 1013 "REGEXP_SUBSTR", 1014 expression.this, 1015 expression.expression, 1016 position, 1017 occurrence, 1018 parameters, 1019 group, 1020 ) 1021 1022 def describe_sql(self, expression: exp.Describe) -> str: 1023 # Default to table if kind is unknown 1024 kind_value = expression.args.get("kind") or "TABLE" 1025 kind = f" {kind_value}" if kind_value else "" 1026 this = f" {self.sql(expression, 'this')}" 1027 expressions = self.expressions(expression, flat=True) 1028 expressions = f" {expressions}" if expressions else "" 1029 return f"DESCRIBE{kind}{this}{expressions}" 1030 1031 def generatedasidentitycolumnconstraint_sql( 1032 self, expression: exp.GeneratedAsIdentityColumnConstraint 1033 ) -> str: 1034 start = expression.args.get("start") 1035 start = f" START {start}" if start else "" 1036 increment = expression.args.get("increment") 1037 increment = f" INCREMENT {increment}" if increment else "" 1038 return f"AUTOINCREMENT{start}{increment}" 1039 1040 def swaptable_sql(self, expression: exp.SwapTable) -> str: 1041 this = self.sql(expression, "this") 1042 return f"SWAP WITH {this}" 1043 1044 def cluster_sql(self, expression: exp.Cluster) -> str: 1045 return f"CLUSTER BY ({self.expressions(expression, flat=True)})" 1046 1047 def struct_sql(self, expression: exp.Struct) -> str: 1048 keys = [] 1049 values = [] 1050 1051 for i, e in enumerate(expression.expressions): 1052 if isinstance(e, exp.PropertyEQ): 1053 keys.append( 1054 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1055 ) 1056 values.append(e.expression) 1057 else: 1058 keys.append(exp.Literal.string(f"_{i}")) 1059 values.append(e) 1060 1061 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 1062 1063 @generator.unsupported_args("weight", "accuracy") 1064 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 1065 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 1066 1067 def alterset_sql(self, expression: exp.AlterSet) -> str: 1068 exprs = self.expressions(expression, flat=True) 1069 exprs = f" {exprs}" if exprs else "" 1070 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1071 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1072 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1073 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1074 tag = self.expressions(expression, key="tag", flat=True) 1075 tag = f" TAG {tag}" if tag else "" 1076 1077 return f"SET{exprs}{file_format}{copy_options}{tag}"
Specifies the strategy according to which identifiers should be normalized.
Default NULL
ordering method to use if not explicitly set.
Possible values: "nulls_are_small"
, "nulls_are_large"
, "nulls_are_last"
Some dialects, such as Snowflake, allow you to reference a CTE column alias in the HAVING clause of the CTE. This flag will cause the CTE alias columns to override any projection aliases in the subquery.
For example, WITH y(c) AS ( SELECT SUM(a) FROM (SELECT 1 a) AS x HAVING c > 0 ) SELECT c FROM y;
will be rewritten as
WITH y(c) AS (
SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0
) SELECT c FROM y;
Associates this dialect's time formats with their equivalent Python strftime
formats.
268 def quote_identifier(self, expression: E, identify: bool = True) -> E: 269 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 270 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 271 if ( 272 isinstance(expression, exp.Identifier) 273 and isinstance(expression.parent, exp.Table) 274 and expression.name.lower() == "dual" 275 ): 276 return expression # type: ignore 277 278 return super().quote_identifier(expression, identify=identify)
Adds quotes to a given identifier.
Arguments:
- expression: The expression of interest. If it's not an
Identifier
, this method is a no-op. - identify: If set to
False
, the quotes will only be added if the identifier is deemed "unsafe", with respect to its characters and this dialect's normalization strategy.
Mapping of an escaped sequence (\n
) to its unescaped version (
).
Inherited Members
- sqlglot.dialects.dialect.Dialect
- Dialect
- INDEX_OFFSET
- WEEK_OFFSET
- UNNEST_COLUMN_ONLY
- ALIAS_POST_TABLESAMPLE
- IDENTIFIERS_CAN_START_WITH_DIGIT
- DPIPE_IS_STRING_CONCAT
- STRICT_STRING_CONCAT
- NORMALIZE_FUNCTIONS
- LOG_BASE_FIRST
- TYPED_DIVISION
- SAFE_DIVISION
- CONCAT_COALESCE
- HEX_LOWERCASE
- DATE_FORMAT
- DATEINT_FORMAT
- FORMAT_MAPPING
- PSEUDOCOLUMNS
- FORCE_EARLY_ALIAS_REF_EXPANSION
- EXPAND_ALIAS_REFS_EARLY_ONLY_IN_GROUP_BY
- SUPPORTS_ORDER_BY_ALL
- HAS_DISTINCT_ARRAY_CONSTRUCTORS
- SUPPORTS_FIXED_SIZE_ARRAYS
- STRICT_JSON_PATH_SYNTAX
- ON_CONDITION_EMPTY_BEFORE_ERROR
- REGEXP_EXTRACT_DEFAULT_GROUP
- SET_OP_DISTINCT_BY_DEFAULT
- CREATABLE_KIND_MAPPING
- DATE_PART_MAPPING
- TYPE_TO_EXPRESSIONS
- ANNOTATORS
- get_or_raise
- format_time
- settings
- normalize_identifier
- case_sensitive
- can_identify
- to_json_path
- parse
- parse_into
- generate
- transpile
- tokenize
- tokenizer
- jsonpath_tokenizer
- parser
- generator
280 class Parser(parser.Parser): 281 IDENTIFY_PIVOT_STRINGS = True 282 DEFAULT_SAMPLING_METHOD = "BERNOULLI" 283 COLON_IS_VARIANT_EXTRACT = True 284 285 ID_VAR_TOKENS = { 286 *parser.Parser.ID_VAR_TOKENS, 287 TokenType.MATCH_CONDITION, 288 } 289 290 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 291 TABLE_ALIAS_TOKENS.discard(TokenType.MATCH_CONDITION) 292 293 FUNCTIONS = { 294 **parser.Parser.FUNCTIONS, 295 "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list, 296 "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args), 297 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 298 this=seq_get(args, 1), expression=seq_get(args, 0) 299 ), 300 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 301 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 302 start=seq_get(args, 0), 303 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 304 step=seq_get(args, 2), 305 ), 306 "BITXOR": binary_from_function(exp.BitwiseXor), 307 "BIT_XOR": binary_from_function(exp.BitwiseXor), 308 "BOOLXOR": binary_from_function(exp.Xor), 309 "DATE": _build_datetime("DATE", exp.DataType.Type.DATE), 310 "DATE_TRUNC": _date_trunc_to_time, 311 "DATEADD": _build_date_time_add(exp.DateAdd), 312 "DATEDIFF": _build_datediff, 313 "DIV0": _build_if_from_div0, 314 "FLATTEN": exp.Explode.from_arg_list, 315 "GET_PATH": lambda args, dialect: exp.JSONExtract( 316 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 317 ), 318 "IFF": exp.If.from_arg_list, 319 "LAST_DAY": lambda args: exp.LastDay( 320 this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1)) 321 ), 322 "LEN": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 323 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 324 "LISTAGG": exp.GroupConcat.from_arg_list, 325 "MEDIAN": lambda args: exp.PercentileCont( 326 this=seq_get(args, 0), expression=exp.Literal.number(0.5) 327 ), 328 "NULLIFZERO": _build_if_from_nullifzero, 329 "OBJECT_CONSTRUCT": _build_object_construct, 330 "REGEXP_REPLACE": _build_regexp_replace, 331 "REGEXP_SUBSTR": lambda args: exp.RegexpExtract( 332 this=seq_get(args, 0), 333 expression=seq_get(args, 1), 334 position=seq_get(args, 2), 335 occurrence=seq_get(args, 3), 336 parameters=seq_get(args, 4), 337 group=seq_get(args, 5) or exp.Literal.number(0), 338 ), 339 "RLIKE": exp.RegexpLike.from_arg_list, 340 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 341 "TIMEADD": _build_date_time_add(exp.TimeAdd), 342 "TIMEDIFF": _build_datediff, 343 "TIMESTAMPADD": _build_date_time_add(exp.DateAdd), 344 "TIMESTAMPDIFF": _build_datediff, 345 "TIMESTAMPFROMPARTS": build_timestamp_from_parts, 346 "TIMESTAMP_FROM_PARTS": build_timestamp_from_parts, 347 "TRY_PARSE_JSON": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True), 348 "TRY_TO_DATE": _build_datetime("TRY_TO_DATE", exp.DataType.Type.DATE, safe=True), 349 "TO_DATE": _build_datetime("TO_DATE", exp.DataType.Type.DATE), 350 "TO_NUMBER": lambda args: exp.ToNumber( 351 this=seq_get(args, 0), 352 format=seq_get(args, 1), 353 precision=seq_get(args, 2), 354 scale=seq_get(args, 3), 355 ), 356 "TO_TIME": _build_datetime("TO_TIME", exp.DataType.Type.TIME), 357 "TO_TIMESTAMP": _build_datetime("TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP), 358 "TO_TIMESTAMP_LTZ": _build_datetime("TO_TIMESTAMP_LTZ", exp.DataType.Type.TIMESTAMPLTZ), 359 "TO_TIMESTAMP_NTZ": _build_datetime("TO_TIMESTAMP_NTZ", exp.DataType.Type.TIMESTAMP), 360 "TO_TIMESTAMP_TZ": _build_datetime("TO_TIMESTAMP_TZ", exp.DataType.Type.TIMESTAMPTZ), 361 "TO_VARCHAR": exp.ToChar.from_arg_list, 362 "ZEROIFNULL": _build_if_from_zeroifnull, 363 } 364 365 FUNCTION_PARSERS = { 366 **parser.Parser.FUNCTION_PARSERS, 367 "DATE_PART": lambda self: self._parse_date_part(), 368 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 369 } 370 FUNCTION_PARSERS.pop("TRIM") 371 372 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 373 374 RANGE_PARSERS = { 375 **parser.Parser.RANGE_PARSERS, 376 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 377 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 378 } 379 380 ALTER_PARSERS = { 381 **parser.Parser.ALTER_PARSERS, 382 "UNSET": lambda self: self.expression( 383 exp.Set, 384 tag=self._match_text_seq("TAG"), 385 expressions=self._parse_csv(self._parse_id_var), 386 unset=True, 387 ), 388 "SWAP": lambda self: self._parse_alter_table_swap(), 389 } 390 391 STATEMENT_PARSERS = { 392 **parser.Parser.STATEMENT_PARSERS, 393 TokenType.SHOW: lambda self: self._parse_show(), 394 } 395 396 PROPERTY_PARSERS = { 397 **parser.Parser.PROPERTY_PARSERS, 398 "LOCATION": lambda self: self._parse_location_property(), 399 } 400 401 TYPE_CONVERTERS = { 402 # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number 403 exp.DataType.Type.DECIMAL: build_default_decimal_type(precision=38, scale=0), 404 } 405 406 SHOW_PARSERS = { 407 "SCHEMAS": _show_parser("SCHEMAS"), 408 "TERSE SCHEMAS": _show_parser("SCHEMAS"), 409 "OBJECTS": _show_parser("OBJECTS"), 410 "TERSE OBJECTS": _show_parser("OBJECTS"), 411 "TABLES": _show_parser("TABLES"), 412 "TERSE TABLES": _show_parser("TABLES"), 413 "VIEWS": _show_parser("VIEWS"), 414 "TERSE VIEWS": _show_parser("VIEWS"), 415 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 416 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 417 "IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 418 "TERSE IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 419 "UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 420 "TERSE UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 421 "SEQUENCES": _show_parser("SEQUENCES"), 422 "TERSE SEQUENCES": _show_parser("SEQUENCES"), 423 "COLUMNS": _show_parser("COLUMNS"), 424 "USERS": _show_parser("USERS"), 425 "TERSE USERS": _show_parser("USERS"), 426 } 427 428 CONSTRAINT_PARSERS = { 429 **parser.Parser.CONSTRAINT_PARSERS, 430 "WITH": lambda self: self._parse_with_constraint(), 431 "MASKING": lambda self: self._parse_with_constraint(), 432 "PROJECTION": lambda self: self._parse_with_constraint(), 433 "TAG": lambda self: self._parse_with_constraint(), 434 } 435 436 STAGED_FILE_SINGLE_TOKENS = { 437 TokenType.DOT, 438 TokenType.MOD, 439 TokenType.SLASH, 440 } 441 442 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 443 444 SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"} 445 446 NON_TABLE_CREATABLES = {"STORAGE INTEGRATION", "TAG", "WAREHOUSE", "STREAMLIT"} 447 448 LAMBDAS = { 449 **parser.Parser.LAMBDAS, 450 TokenType.ARROW: lambda self, expressions: self.expression( 451 exp.Lambda, 452 this=self._replace_lambda( 453 self._parse_assignment(), 454 expressions, 455 ), 456 expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions], 457 ), 458 } 459 460 def _negate_range( 461 self, this: t.Optional[exp.Expression] = None 462 ) -> t.Optional[exp.Expression]: 463 if not this: 464 return this 465 466 query = this.args.get("query") 467 if isinstance(this, exp.In) and isinstance(query, exp.Query): 468 # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so 469 # we do this conversion here to avoid parsing it into `NOT value IN (subquery)` 470 # which can produce different results (most likely a SnowFlake bug). 471 # 472 # https://docs.snowflake.com/en/sql-reference/functions/in 473 # Context: https://github.com/tobymao/sqlglot/issues/3890 474 return self.expression( 475 exp.NEQ, this=this.this, expression=exp.All(this=query.unnest()) 476 ) 477 478 return self.expression(exp.Not, this=this) 479 480 def _parse_with_constraint(self) -> t.Optional[exp.Expression]: 481 if self._prev.token_type != TokenType.WITH: 482 self._retreat(self._index - 1) 483 484 if self._match_text_seq("MASKING", "POLICY"): 485 policy = self._parse_column() 486 return self.expression( 487 exp.MaskingPolicyColumnConstraint, 488 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 489 expressions=self._match(TokenType.USING) 490 and self._parse_wrapped_csv(self._parse_id_var), 491 ) 492 if self._match_text_seq("PROJECTION", "POLICY"): 493 policy = self._parse_column() 494 return self.expression( 495 exp.ProjectionPolicyColumnConstraint, 496 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 497 ) 498 if self._match(TokenType.TAG): 499 return self.expression( 500 exp.TagColumnConstraint, 501 expressions=self._parse_wrapped_csv(self._parse_property), 502 ) 503 504 return None 505 506 def _parse_create(self) -> exp.Create | exp.Command: 507 expression = super()._parse_create() 508 if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES: 509 # Replace the Table node with the enclosed Identifier 510 expression.this.replace(expression.this.this) 511 512 return expression 513 514 # https://docs.snowflake.com/en/sql-reference/functions/date_part.html 515 # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 516 def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 517 this = self._parse_var() or self._parse_type() 518 519 if not this: 520 return None 521 522 self._match(TokenType.COMMA) 523 expression = self._parse_bitwise() 524 this = map_date_part(this) 525 name = this.name.upper() 526 527 if name.startswith("EPOCH"): 528 if name == "EPOCH_MILLISECOND": 529 scale = 10**3 530 elif name == "EPOCH_MICROSECOND": 531 scale = 10**6 532 elif name == "EPOCH_NANOSECOND": 533 scale = 10**9 534 else: 535 scale = None 536 537 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 538 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 539 540 if scale: 541 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 542 543 return to_unix 544 545 return self.expression(exp.Extract, this=this, expression=expression) 546 547 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 548 if is_map: 549 # Keys are strings in Snowflake's objects, see also: 550 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 551 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 552 return self._parse_slice(self._parse_string()) 553 554 return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True)) 555 556 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 557 lateral = super()._parse_lateral() 558 if not lateral: 559 return lateral 560 561 if isinstance(lateral.this, exp.Explode): 562 table_alias = lateral.args.get("alias") 563 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 564 if table_alias and not table_alias.args.get("columns"): 565 table_alias.set("columns", columns) 566 elif not table_alias: 567 exp.alias_(lateral, "_flattened", table=columns, copy=False) 568 569 return lateral 570 571 def _parse_table_parts( 572 self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False 573 ) -> exp.Table: 574 # https://docs.snowflake.com/en/user-guide/querying-stage 575 if self._match(TokenType.STRING, advance=False): 576 table = self._parse_string() 577 elif self._match_text_seq("@", advance=False): 578 table = self._parse_location_path() 579 else: 580 table = None 581 582 if table: 583 file_format = None 584 pattern = None 585 586 wrapped = self._match(TokenType.L_PAREN) 587 while self._curr and wrapped and not self._match(TokenType.R_PAREN): 588 if self._match_text_seq("FILE_FORMAT", "=>"): 589 file_format = self._parse_string() or super()._parse_table_parts( 590 is_db_reference=is_db_reference 591 ) 592 elif self._match_text_seq("PATTERN", "=>"): 593 pattern = self._parse_string() 594 else: 595 break 596 597 self._match(TokenType.COMMA) 598 599 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 600 else: 601 table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 602 603 return table 604 605 def _parse_id_var( 606 self, 607 any_token: bool = True, 608 tokens: t.Optional[t.Collection[TokenType]] = None, 609 ) -> t.Optional[exp.Expression]: 610 if self._match_text_seq("IDENTIFIER", "("): 611 identifier = ( 612 super()._parse_id_var(any_token=any_token, tokens=tokens) 613 or self._parse_string() 614 ) 615 self._match_r_paren() 616 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 617 618 return super()._parse_id_var(any_token=any_token, tokens=tokens) 619 620 def _parse_show_snowflake(self, this: str) -> exp.Show: 621 scope = None 622 scope_kind = None 623 624 # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS 625 # which is syntactically valid but has no effect on the output 626 terse = self._tokens[self._index - 2].text.upper() == "TERSE" 627 628 history = self._match_text_seq("HISTORY") 629 630 like = self._parse_string() if self._match(TokenType.LIKE) else None 631 632 if self._match(TokenType.IN): 633 if self._match_text_seq("ACCOUNT"): 634 scope_kind = "ACCOUNT" 635 elif self._match_set(self.DB_CREATABLES): 636 scope_kind = self._prev.text.upper() 637 if self._curr: 638 scope = self._parse_table_parts() 639 elif self._curr: 640 scope_kind = "SCHEMA" if this in self.SCHEMA_KINDS else "TABLE" 641 scope = self._parse_table_parts() 642 643 return self.expression( 644 exp.Show, 645 **{ 646 "terse": terse, 647 "this": this, 648 "history": history, 649 "like": like, 650 "scope": scope, 651 "scope_kind": scope_kind, 652 "starts_with": self._match_text_seq("STARTS", "WITH") and self._parse_string(), 653 "limit": self._parse_limit(), 654 "from": self._parse_string() if self._match(TokenType.FROM) else None, 655 }, 656 ) 657 658 def _parse_alter_table_swap(self) -> exp.SwapTable: 659 self._match_text_seq("WITH") 660 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 661 662 def _parse_location_property(self) -> exp.LocationProperty: 663 self._match(TokenType.EQ) 664 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 665 666 def _parse_file_location(self) -> t.Optional[exp.Expression]: 667 # Parse either a subquery or a staged file 668 return ( 669 self._parse_select(table=True, parse_subquery_alias=False) 670 if self._match(TokenType.L_PAREN, advance=False) 671 else self._parse_table_parts() 672 ) 673 674 def _parse_location_path(self) -> exp.Var: 675 parts = [self._advance_any(ignore_reserved=True)] 676 677 # We avoid consuming a comma token because external tables like @foo and @bar 678 # can be joined in a query with a comma separator, as well as closing paren 679 # in case of subqueries 680 while self._is_connected() and not self._match_set( 681 (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False 682 ): 683 parts.append(self._advance_any(ignore_reserved=True)) 684 685 return exp.var("".join(part.text for part in parts if part)) 686 687 def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: 688 this = super()._parse_lambda_arg() 689 690 if not this: 691 return this 692 693 typ = self._parse_types() 694 695 if typ: 696 return self.expression(exp.Cast, this=this, to=typ) 697 698 return this
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
- NO_PAREN_FUNCTIONS
- 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
- INTERVAL_VARS
- ALIAS_TOKENS
- ARRAY_CONSTRUCTORS
- COMMENT_TABLE_ALIAS_TOKENS
- UPDATE_ALIAS_TOKENS
- TRIM_TYPES
- FUNC_TOKENS
- CONJUNCTION
- ASSIGNMENT
- DISJUNCTION
- EQUALITY
- COMPARISON
- BITWISE
- TERM
- FACTOR
- EXPONENT
- TIMES
- SET_OPERATIONS
- JOIN_METHODS
- JOIN_SIDES
- JOIN_KINDS
- JOIN_HINTS
- COLUMN_OPERATORS
- EXPRESSION_PARSERS
- UNARY_PARSERS
- STRING_PARSERS
- NUMERIC_PARSERS
- PRIMARY_PARSERS
- PLACEHOLDER_PARSERS
- ALTER_ALTER_PARSERS
- SCHEMA_UNNAMED_CONSTRAINTS
- NO_PAREN_FUNCTION_PARSERS
- INVALID_FUNC_NAME_TOKENS
- FUNCTIONS_WITH_ALIASED_ARGS
- KEY_VALUE_DEFINITIONS
- QUERY_MODIFIER_PARSERS
- SET_PARSERS
- TYPE_LITERAL_PARSERS
- DDL_SELECT_TOKENS
- PRE_VOLATILE_TOKENS
- TRANSACTION_KIND
- TRANSACTION_CHARACTERISTICS
- CONFLICT_ACTIONS
- CREATE_SEQUENCE
- ISOLATED_LOADING_OPTIONS
- USABLES
- CAST_ACTIONS
- SCHEMA_BINDING_OPTIONS
- KEY_CONSTRAINT_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
- NULL_TOKENS
- UNNEST_OFFSET_ALIAS_TOKENS
- SELECT_START_TOKENS
- COPY_INTO_VARLEN_OPTIONS
- IS_JSON_PREDICATE_KIND
- ODBC_DATETIME_LITERALS
- ON_CONDITION_TOKENS
- STRICT_CAST
- PREFIXED_PIVOT_COLUMNS
- LOG_DEFAULTS_TO_LN
- ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
- TABLESAMPLE_CSV
- SET_REQUIRES_ASSIGNMENT_DELIMITER
- TRIM_PATTERN_FIRST
- STRING_ALIASES
- MODIFIERS_ATTACHED_TO_SET_OP
- SET_OP_MODIFIERS
- NO_PAREN_IF_COMMANDS
- JSON_ARROWS_REQUIRE_JSON_TYPE
- VALUES_FOLLOWED_BY_PAREN
- SUPPORTS_IMPLICIT_UNNEST
- INTERVAL_SPANS
- SUPPORTS_PARTITION_SELECTION
- error_level
- error_message_context
- max_errors
- dialect
- reset
- parse
- parse_into
- check_errors
- raise_error
- expression
- validate_expression
- errors
- sql
700 class Tokenizer(tokens.Tokenizer): 701 STRING_ESCAPES = ["\\", "'"] 702 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 703 RAW_STRINGS = ["$$"] 704 COMMENTS = ["--", "//", ("/*", "*/")] 705 NESTED_COMMENTS = False 706 707 KEYWORDS = { 708 **tokens.Tokenizer.KEYWORDS, 709 "BYTEINT": TokenType.INT, 710 "CHAR VARYING": TokenType.VARCHAR, 711 "CHARACTER VARYING": TokenType.VARCHAR, 712 "EXCLUDE": TokenType.EXCEPT, 713 "ILIKE ANY": TokenType.ILIKE_ANY, 714 "LIKE ANY": TokenType.LIKE_ANY, 715 "MATCH_CONDITION": TokenType.MATCH_CONDITION, 716 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 717 "MINUS": TokenType.EXCEPT, 718 "NCHAR VARYING": TokenType.VARCHAR, 719 "PUT": TokenType.COMMAND, 720 "REMOVE": TokenType.COMMAND, 721 "RM": TokenType.COMMAND, 722 "SAMPLE": TokenType.TABLE_SAMPLE, 723 "SQL_DOUBLE": TokenType.DOUBLE, 724 "SQL_VARCHAR": TokenType.VARCHAR, 725 "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION, 726 "TAG": TokenType.TAG, 727 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 728 "TOP": TokenType.TOP, 729 "WAREHOUSE": TokenType.WAREHOUSE, 730 "STREAMLIT": TokenType.STREAMLIT, 731 } 732 KEYWORDS.pop("/*+") 733 734 SINGLE_TOKENS = { 735 **tokens.Tokenizer.SINGLE_TOKENS, 736 "$": TokenType.PARAMETER, 737 } 738 739 VAR_SINGLE_TOKENS = {"$"} 740 741 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- BIT_STRINGS
- BYTE_STRINGS
- HEREDOC_STRINGS
- UNICODE_STRINGS
- IDENTIFIERS
- IDENTIFIER_ESCAPES
- QUOTES
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- WHITE_SPACE
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- dialect
- reset
- tokenize
- tokenize_rs
- size
- sql
- tokens
743 class Generator(generator.Generator): 744 PARAMETER_TOKEN = "$" 745 MATCHED_BY_SOURCE = False 746 SINGLE_STRING_INTERVAL = True 747 JOIN_HINTS = False 748 TABLE_HINTS = False 749 QUERY_HINTS = False 750 AGGREGATE_FILTER_SUPPORTED = False 751 SUPPORTS_TABLE_COPY = False 752 COLLATE_IS_FUNC = True 753 LIMIT_ONLY_LITERALS = True 754 JSON_KEY_VALUE_PAIR_SEP = "," 755 INSERT_OVERWRITE = " OVERWRITE INTO" 756 STRUCT_DELIMITER = ("(", ")") 757 COPY_PARAMS_ARE_WRAPPED = False 758 COPY_PARAMS_EQ_REQUIRED = True 759 STAR_EXCEPT = "EXCLUDE" 760 SUPPORTS_EXPLODING_PROJECTIONS = False 761 ARRAY_CONCAT_IS_VAR_LEN = False 762 SUPPORTS_CONVERT_TIMEZONE = True 763 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 764 765 TRANSFORMS = { 766 **generator.Generator.TRANSFORMS, 767 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 768 exp.ArgMax: rename_func("MAX_BY"), 769 exp.ArgMin: rename_func("MIN_BY"), 770 exp.Array: inline_array_sql, 771 exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"), 772 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 773 exp.AtTimeZone: lambda self, e: self.func( 774 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 775 ), 776 exp.BitwiseXor: rename_func("BITXOR"), 777 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 778 exp.DateAdd: date_delta_sql("DATEADD"), 779 exp.DateDiff: date_delta_sql("DATEDIFF"), 780 exp.DateStrToDate: datestrtodate_sql, 781 exp.DayOfMonth: rename_func("DAYOFMONTH"), 782 exp.DayOfWeek: rename_func("DAYOFWEEK"), 783 exp.DayOfYear: rename_func("DAYOFYEAR"), 784 exp.Explode: rename_func("FLATTEN"), 785 exp.Extract: rename_func("DATE_PART"), 786 exp.FromTimeZone: lambda self, e: self.func( 787 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 788 ), 789 exp.GenerateSeries: lambda self, e: self.func( 790 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 791 ), 792 exp.GroupConcat: rename_func("LISTAGG"), 793 exp.If: if_sql(name="IFF", false_value="NULL"), 794 exp.JSONExtract: lambda self, e: self.func("GET_PATH", e.this, e.expression), 795 exp.JSONExtractScalar: lambda self, e: self.func( 796 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 797 ), 798 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 799 exp.JSONPathRoot: lambda *_: "", 800 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 801 exp.LogicalOr: rename_func("BOOLOR_AGG"), 802 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 803 exp.Max: max_or_greatest, 804 exp.Min: min_or_least, 805 exp.ParseJSON: lambda self, e: self.func( 806 "TRY_PARSE_JSON" if e.args.get("safe") else "PARSE_JSON", e.this 807 ), 808 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 809 exp.PercentileCont: transforms.preprocess( 810 [transforms.add_within_group_for_percentiles] 811 ), 812 exp.PercentileDisc: transforms.preprocess( 813 [transforms.add_within_group_for_percentiles] 814 ), 815 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 816 exp.RegexpILike: _regexpilike_sql, 817 exp.Rand: rename_func("RANDOM"), 818 exp.Select: transforms.preprocess( 819 [ 820 transforms.eliminate_distinct_on, 821 transforms.explode_to_unnest(), 822 transforms.eliminate_semi_and_anti_joins, 823 _unnest_generate_date_array, 824 ] 825 ), 826 exp.SHA: rename_func("SHA1"), 827 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 828 exp.StartsWith: rename_func("STARTSWITH"), 829 exp.StrPosition: lambda self, e: self.func( 830 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 831 ), 832 exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)), 833 exp.Stuff: rename_func("INSERT"), 834 exp.TimeAdd: date_delta_sql("TIMEADD"), 835 exp.TimestampDiff: lambda self, e: self.func( 836 "TIMESTAMPDIFF", e.unit, e.expression, e.this 837 ), 838 exp.TimestampTrunc: timestamptrunc_sql(), 839 exp.TimeStrToTime: timestrtotime_sql, 840 exp.TimeToStr: lambda self, e: self.func( 841 "TO_CHAR", exp.cast(e.this, exp.DataType.Type.TIMESTAMP), self.format_time(e) 842 ), 843 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 844 exp.ToArray: rename_func("TO_ARRAY"), 845 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 846 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 847 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 848 exp.TsOrDsToDate: lambda self, e: self.func( 849 "TRY_TO_DATE" if e.args.get("safe") else "TO_DATE", e.this, self.format_time(e) 850 ), 851 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 852 exp.Uuid: rename_func("UUID_STRING"), 853 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 854 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 855 exp.Xor: rename_func("BOOLXOR"), 856 } 857 858 SUPPORTED_JSON_PATH_PARTS = { 859 exp.JSONPathKey, 860 exp.JSONPathRoot, 861 exp.JSONPathSubscript, 862 } 863 864 TYPE_MAPPING = { 865 **generator.Generator.TYPE_MAPPING, 866 exp.DataType.Type.NESTED: "OBJECT", 867 exp.DataType.Type.STRUCT: "OBJECT", 868 } 869 870 PROPERTIES_LOCATION = { 871 **generator.Generator.PROPERTIES_LOCATION, 872 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 873 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 874 } 875 876 UNSUPPORTED_VALUES_EXPRESSIONS = { 877 exp.Map, 878 exp.StarMap, 879 exp.Struct, 880 exp.VarMap, 881 } 882 883 def with_properties(self, properties: exp.Properties) -> str: 884 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 885 886 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 887 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 888 values_as_table = False 889 890 return super().values_sql(expression, values_as_table=values_as_table) 891 892 def datatype_sql(self, expression: exp.DataType) -> str: 893 expressions = expression.expressions 894 if ( 895 expressions 896 and expression.is_type(*exp.DataType.STRUCT_TYPES) 897 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 898 ): 899 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 900 return "OBJECT" 901 902 return super().datatype_sql(expression) 903 904 def tonumber_sql(self, expression: exp.ToNumber) -> str: 905 return self.func( 906 "TO_NUMBER", 907 expression.this, 908 expression.args.get("format"), 909 expression.args.get("precision"), 910 expression.args.get("scale"), 911 ) 912 913 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 914 milli = expression.args.get("milli") 915 if milli is not None: 916 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 917 expression.set("nano", milli_to_nano) 918 919 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 920 921 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 922 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 923 return self.func("TO_GEOGRAPHY", expression.this) 924 if expression.is_type(exp.DataType.Type.GEOMETRY): 925 return self.func("TO_GEOMETRY", expression.this) 926 927 return super().cast_sql(expression, safe_prefix=safe_prefix) 928 929 def trycast_sql(self, expression: exp.TryCast) -> str: 930 value = expression.this 931 932 if value.type is None: 933 from sqlglot.optimizer.annotate_types import annotate_types 934 935 value = annotate_types(value) 936 937 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 938 return super().trycast_sql(expression) 939 940 # TRY_CAST only works for string values in Snowflake 941 return self.cast_sql(expression) 942 943 def log_sql(self, expression: exp.Log) -> str: 944 if not expression.expression: 945 return self.func("LN", expression.this) 946 947 return super().log_sql(expression) 948 949 def unnest_sql(self, expression: exp.Unnest) -> str: 950 unnest_alias = expression.args.get("alias") 951 offset = expression.args.get("offset") 952 953 columns = [ 954 exp.to_identifier("seq"), 955 exp.to_identifier("key"), 956 exp.to_identifier("path"), 957 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 958 seq_get(unnest_alias.columns if unnest_alias else [], 0) 959 or exp.to_identifier("value"), 960 exp.to_identifier("this"), 961 ] 962 963 if unnest_alias: 964 unnest_alias.set("columns", columns) 965 else: 966 unnest_alias = exp.TableAlias(this="_u", columns=columns) 967 968 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 969 alias = self.sql(unnest_alias) 970 alias = f" AS {alias}" if alias else "" 971 return f"{explode}{alias}" 972 973 def show_sql(self, expression: exp.Show) -> str: 974 terse = "TERSE " if expression.args.get("terse") else "" 975 history = " HISTORY" if expression.args.get("history") else "" 976 like = self.sql(expression, "like") 977 like = f" LIKE {like}" if like else "" 978 979 scope = self.sql(expression, "scope") 980 scope = f" {scope}" if scope else "" 981 982 scope_kind = self.sql(expression, "scope_kind") 983 if scope_kind: 984 scope_kind = f" IN {scope_kind}" 985 986 starts_with = self.sql(expression, "starts_with") 987 if starts_with: 988 starts_with = f" STARTS WITH {starts_with}" 989 990 limit = self.sql(expression, "limit") 991 992 from_ = self.sql(expression, "from") 993 if from_: 994 from_ = f" FROM {from_}" 995 996 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}" 997 998 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 999 # Other dialects don't support all of the following parameters, so we need to 1000 # generate default values as necessary to ensure the transpilation is correct 1001 group = expression.args.get("group") 1002 1003 # To avoid generating all these default values, we set group to None if 1004 # it's 0 (also default value) which doesn't trigger the following chain 1005 if group and group.name == "0": 1006 group = None 1007 1008 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 1009 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 1010 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 1011 1012 return self.func( 1013 "REGEXP_SUBSTR", 1014 expression.this, 1015 expression.expression, 1016 position, 1017 occurrence, 1018 parameters, 1019 group, 1020 ) 1021 1022 def describe_sql(self, expression: exp.Describe) -> str: 1023 # Default to table if kind is unknown 1024 kind_value = expression.args.get("kind") or "TABLE" 1025 kind = f" {kind_value}" if kind_value else "" 1026 this = f" {self.sql(expression, 'this')}" 1027 expressions = self.expressions(expression, flat=True) 1028 expressions = f" {expressions}" if expressions else "" 1029 return f"DESCRIBE{kind}{this}{expressions}" 1030 1031 def generatedasidentitycolumnconstraint_sql( 1032 self, expression: exp.GeneratedAsIdentityColumnConstraint 1033 ) -> str: 1034 start = expression.args.get("start") 1035 start = f" START {start}" if start else "" 1036 increment = expression.args.get("increment") 1037 increment = f" INCREMENT {increment}" if increment else "" 1038 return f"AUTOINCREMENT{start}{increment}" 1039 1040 def swaptable_sql(self, expression: exp.SwapTable) -> str: 1041 this = self.sql(expression, "this") 1042 return f"SWAP WITH {this}" 1043 1044 def cluster_sql(self, expression: exp.Cluster) -> str: 1045 return f"CLUSTER BY ({self.expressions(expression, flat=True)})" 1046 1047 def struct_sql(self, expression: exp.Struct) -> str: 1048 keys = [] 1049 values = [] 1050 1051 for i, e in enumerate(expression.expressions): 1052 if isinstance(e, exp.PropertyEQ): 1053 keys.append( 1054 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1055 ) 1056 values.append(e.expression) 1057 else: 1058 keys.append(exp.Literal.string(f"_{i}")) 1059 values.append(e) 1060 1061 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 1062 1063 @generator.unsupported_args("weight", "accuracy") 1064 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 1065 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 1066 1067 def alterset_sql(self, expression: exp.AlterSet) -> str: 1068 exprs = self.expressions(expression, flat=True) 1069 exprs = f" {exprs}" if exprs else "" 1070 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1071 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1072 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1073 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1074 tag = self.expressions(expression, key="tag", flat=True) 1075 tag = f" TAG {tag}" if tag else "" 1076 1077 return f"SET{exprs}{file_format}{copy_options}{tag}"
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 or 'always': Always quote. '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
WHERE
clause. 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
892 def datatype_sql(self, expression: exp.DataType) -> str: 893 expressions = expression.expressions 894 if ( 895 expressions 896 and expression.is_type(*exp.DataType.STRUCT_TYPES) 897 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 898 ): 899 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 900 return "OBJECT" 901 902 return super().datatype_sql(expression)
913 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 914 milli = expression.args.get("milli") 915 if milli is not None: 916 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 917 expression.set("nano", milli_to_nano) 918 919 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
921 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 922 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 923 return self.func("TO_GEOGRAPHY", expression.this) 924 if expression.is_type(exp.DataType.Type.GEOMETRY): 925 return self.func("TO_GEOMETRY", expression.this) 926 927 return super().cast_sql(expression, safe_prefix=safe_prefix)
929 def trycast_sql(self, expression: exp.TryCast) -> str: 930 value = expression.this 931 932 if value.type is None: 933 from sqlglot.optimizer.annotate_types import annotate_types 934 935 value = annotate_types(value) 936 937 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 938 return super().trycast_sql(expression) 939 940 # TRY_CAST only works for string values in Snowflake 941 return self.cast_sql(expression)
949 def unnest_sql(self, expression: exp.Unnest) -> str: 950 unnest_alias = expression.args.get("alias") 951 offset = expression.args.get("offset") 952 953 columns = [ 954 exp.to_identifier("seq"), 955 exp.to_identifier("key"), 956 exp.to_identifier("path"), 957 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 958 seq_get(unnest_alias.columns if unnest_alias else [], 0) 959 or exp.to_identifier("value"), 960 exp.to_identifier("this"), 961 ] 962 963 if unnest_alias: 964 unnest_alias.set("columns", columns) 965 else: 966 unnest_alias = exp.TableAlias(this="_u", columns=columns) 967 968 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 969 alias = self.sql(unnest_alias) 970 alias = f" AS {alias}" if alias else "" 971 return f"{explode}{alias}"
973 def show_sql(self, expression: exp.Show) -> str: 974 terse = "TERSE " if expression.args.get("terse") else "" 975 history = " HISTORY" if expression.args.get("history") else "" 976 like = self.sql(expression, "like") 977 like = f" LIKE {like}" if like else "" 978 979 scope = self.sql(expression, "scope") 980 scope = f" {scope}" if scope else "" 981 982 scope_kind = self.sql(expression, "scope_kind") 983 if scope_kind: 984 scope_kind = f" IN {scope_kind}" 985 986 starts_with = self.sql(expression, "starts_with") 987 if starts_with: 988 starts_with = f" STARTS WITH {starts_with}" 989 990 limit = self.sql(expression, "limit") 991 992 from_ = self.sql(expression, "from") 993 if from_: 994 from_ = f" FROM {from_}" 995 996 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}"
998 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 999 # Other dialects don't support all of the following parameters, so we need to 1000 # generate default values as necessary to ensure the transpilation is correct 1001 group = expression.args.get("group") 1002 1003 # To avoid generating all these default values, we set group to None if 1004 # it's 0 (also default value) which doesn't trigger the following chain 1005 if group and group.name == "0": 1006 group = None 1007 1008 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 1009 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 1010 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 1011 1012 return self.func( 1013 "REGEXP_SUBSTR", 1014 expression.this, 1015 expression.expression, 1016 position, 1017 occurrence, 1018 parameters, 1019 group, 1020 )
1022 def describe_sql(self, expression: exp.Describe) -> str: 1023 # Default to table if kind is unknown 1024 kind_value = expression.args.get("kind") or "TABLE" 1025 kind = f" {kind_value}" if kind_value else "" 1026 this = f" {self.sql(expression, 'this')}" 1027 expressions = self.expressions(expression, flat=True) 1028 expressions = f" {expressions}" if expressions else "" 1029 return f"DESCRIBE{kind}{this}{expressions}"
1031 def generatedasidentitycolumnconstraint_sql( 1032 self, expression: exp.GeneratedAsIdentityColumnConstraint 1033 ) -> str: 1034 start = expression.args.get("start") 1035 start = f" START {start}" if start else "" 1036 increment = expression.args.get("increment") 1037 increment = f" INCREMENT {increment}" if increment else "" 1038 return f"AUTOINCREMENT{start}{increment}"
1047 def struct_sql(self, expression: exp.Struct) -> str: 1048 keys = [] 1049 values = [] 1050 1051 for i, e in enumerate(expression.expressions): 1052 if isinstance(e, exp.PropertyEQ): 1053 keys.append( 1054 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1055 ) 1056 values.append(e.expression) 1057 else: 1058 keys.append(exp.Literal.string(f"_{i}")) 1059 values.append(e) 1060 1061 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
1067 def alterset_sql(self, expression: exp.AlterSet) -> str: 1068 exprs = self.expressions(expression, flat=True) 1069 exprs = f" {exprs}" if exprs else "" 1070 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1071 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1072 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1073 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1074 tag = self.expressions(expression, key="tag", flat=True) 1075 tag = f" TAG {tag}" if tag else "" 1076 1077 return f"SET{exprs}{file_format}{copy_options}{tag}"
Inherited Members
- sqlglot.generator.Generator
- Generator
- NULL_ORDERING_SUPPORTED
- IGNORE_NULLS_IN_FUNC
- LOCKING_READS_SUPPORTED
- WRAP_DERIVED_VALUES
- CREATE_FUNCTION_RETURN_AS
- INTERVAL_ALLOWS_PLURAL_FORM
- LIMIT_FETCH
- RENAME_TABLE_WITH_DB
- GROUPINGS_SEP
- INDEX_ON
- QUERY_HINT_SEP
- IS_BOOL_ALLOWED
- DUPLICATE_KEY_UPDATE_WITH_SET
- LIMIT_IS_TOP
- RETURNING_END
- EXTRACT_ALLOWS_QUOTES
- TZ_TO_WITH_TIME_ZONE
- NVL2_SUPPORTED
- VALUES_AS_TABLE
- ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
- UNNEST_WITH_ORDINALITY
- SEMI_ANTI_JOIN_WITH_SIDE
- COMPUTED_COLUMN_WITH_TYPE
- TABLESAMPLE_REQUIRES_PARENS
- TABLESAMPLE_SIZE_IS_ROWS
- TABLESAMPLE_KEYWORDS
- TABLESAMPLE_WITH_METHOD
- TABLESAMPLE_SEED_KEYWORD
- 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
- SUPPORTS_SELECT_INTO
- SUPPORTS_UNLOGGED_TABLES
- SUPPORTS_CREATE_TABLE_LIKE
- LIKE_PROPERTY_INSIDE_SCHEMA
- MULTI_ARG_DISTINCT
- JSON_TYPE_REQUIRED_FOR_EXTRACTION
- JSON_PATH_BRACKETED_KEY_SUPPORTED
- JSON_PATH_SINGLE_QUOTE_ESCAPE
- CAN_IMPLEMENT_ARRAY_ANY
- SUPPORTS_TO_NUMBER
- SET_OP_MODIFIERS
- COPY_HAS_INTO_KEYWORD
- HEX_FUNC
- WITH_PROPERTIES_PREFIX
- QUOTE_JSON_PATH
- PAD_FILL_PATTERN_IS_REQUIRED
- PARSE_JSON_NAME
- TIME_PART_SINGULARS
- TOKEN_MAPPING
- NAMED_PLACEHOLDER_TOKEN
- RESERVED_KEYWORDS
- WITH_SEPARATED_COMMENTS
- EXCLUDE_COMMENTS
- UNWRAPPED_INTERVAL_VALUES
- PARAMETERIZABLE_TEXT_TYPES
- EXPRESSIONS_WITHOUT_NESTED_CTES
- 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
- pad_comment
- maybe_comment
- wrap
- no_identify
- normalize_func
- indent
- sql
- uncache_sql
- cache_sql
- characterset_sql
- column_parts
- column_sql
- columnposition_sql
- columndef_sql
- columnconstraint_sql
- computedcolumnconstraint_sql
- autoincrementcolumnconstraint_sql
- compresscolumnconstraint_sql
- generatedasrowcolumnconstraint_sql
- periodforsystemtimeconstraint_sql
- notnullcolumnconstraint_sql
- transformcolumnconstraint_sql
- primarykeycolumnconstraint_sql
- uniquecolumnconstraint_sql
- createable_sql
- create_sql
- sequenceproperties_sql
- clone_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
- 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
- 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
- tablesample_sql
- pivot_sql
- version_sql
- tuple_sql
- update_sql
- var_sql
- into_sql
- from_sql
- groupingsets_sql
- rollup_sql
- cube_sql
- group_sql
- having_sql
- connect_sql
- prior_sql
- join_sql
- lambda_sql
- lateral_op
- lateral_sql
- limit_sql
- offset_sql
- setitem_sql
- set_sql
- pragma_sql
- lock_sql
- literal_sql
- escape_str
- loaddata_sql
- null_sql
- boolean_sql
- order_sql
- withfill_sql
- distribute_sql
- sort_sql
- ordered_sql
- matchrecognizemeasure_sql
- matchrecognize_sql
- query_modifiers
- options_modifier
- queryoption_sql
- offset_limit_modifiers
- after_limit_modifiers
- select_sql
- schema_sql
- schema_columns_sql
- star_sql
- parameter_sql
- sessionparameter_sql
- placeholder_sql
- subquery_sql
- qualify_sql
- prewhere_sql
- where_sql
- window_sql
- partition_by_sql
- windowspec_sql
- withingroup_sql
- between_sql
- bracket_offset_expressions
- bracket_sql
- 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
- matchagainst_sql
- jsonkeyvalue_sql
- jsonpath_sql
- json_path_part
- formatjson_sql
- jsonobject_sql
- jsonobjectagg_sql
- jsonarray_sql
- jsonarrayagg_sql
- jsoncolumndef_sql
- jsonschema_sql
- jsontable_sql
- openjsoncolumndef_sql
- openjson_sql
- in_sql
- in_unnest_op
- interval_sql
- 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
- currentdate_sql
- collate_sql
- command_sql
- comment_sql
- mergetreettlaction_sql
- mergetreettl_sql
- transaction_sql
- commit_sql
- rollback_sql
- altercolumn_sql
- alterdiststyle_sql
- altersortkey_sql
- renametable_sql
- renamecolumn_sql
- alter_sql
- add_column_sql
- droppartition_sql
- addconstraint_sql
- distinct_sql
- ignorenulls_sql
- respectnulls_sql
- havingmax_sql
- intdiv_sql
- dpipe_sql
- div_sql
- overlaps_sql
- distance_sql
- dot_sql
- eq_sql
- propertyeq_sql
- escape_sql
- glob_sql
- gt_sql
- gte_sql
- ilike_sql
- ilikeany_sql
- is_sql
- like_sql
- likeany_sql
- similarto_sql
- lt_sql
- lte_sql
- mod_sql
- mul_sql
- neq_sql
- nullsafeeq_sql
- nullsafeneq_sql
- slice_sql
- sub_sql
- try_sql
- use_sql
- binary
- 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
- merge_sql
- tochar_sql
- dictproperty_sql
- dictrange_sql
- dictsubproperty_sql
- duplicatekeyproperty_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
- forin_sql
- refresh_sql
- toarray_sql
- tsordstotime_sql
- tsordstotimestamp_sql
- tsordstodate_sql
- unixdate_sql
- lastday_sql
- dateadd_sql
- arrayany_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
- arrayconcat_sql
- converttimezone_sql
- json_sql
- jsonvalue_sql
- conditionalinsert_sql
- multitableinserts_sql
- oncondition_sql
- jsonexists_sql
- arrayagg_sql