sqlglot.dialects.snowflake
1from __future__ import annotations 2 3import typing as t 4 5from sqlglot import exp, generator, jsonpath, parser, tokens, transforms 6from sqlglot.dialects.dialect import ( 7 Dialect, 8 NormalizationStrategy, 9 build_timetostr_or_tochar, 10 binary_from_function, 11 build_default_decimal_type, 12 build_timestamp_from_parts, 13 date_delta_sql, 14 date_trunc_to_time, 15 datestrtodate_sql, 16 build_formatted_time, 17 if_sql, 18 inline_array_sql, 19 max_or_greatest, 20 min_or_least, 21 rename_func, 22 timestamptrunc_sql, 23 timestrtotime_sql, 24 var_map_sql, 25 map_date_part, 26 no_timestamp_sql, 27 strposition_sql, 28 timestampdiff_sql, 29 no_make_interval_sql, 30 groupconcat_sql, 31) 32from sqlglot.generator import unsupported_args 33from sqlglot.helper import flatten, is_float, is_int, seq_get 34from sqlglot.tokens import TokenType 35 36if t.TYPE_CHECKING: 37 from sqlglot._typing import E, B 38 39 40# from https://docs.snowflake.com/en/sql-reference/functions/to_timestamp.html 41def _build_datetime( 42 name: str, kind: exp.DataType.Type, safe: bool = False 43) -> t.Callable[[t.List], exp.Func]: 44 def _builder(args: t.List) -> exp.Func: 45 value = seq_get(args, 0) 46 scale_or_fmt = seq_get(args, 1) 47 48 int_value = value is not None and is_int(value.name) 49 int_scale_or_fmt = scale_or_fmt is not None and scale_or_fmt.is_int 50 51 if isinstance(value, exp.Literal) or (value and scale_or_fmt): 52 # Converts calls like `TO_TIME('01:02:03')` into casts 53 if len(args) == 1 and value.is_string and not int_value: 54 return ( 55 exp.TryCast(this=value, to=exp.DataType.build(kind)) 56 if safe 57 else exp.cast(value, kind) 58 ) 59 60 # Handles `TO_TIMESTAMP(str, fmt)` and `TO_TIMESTAMP(num, scale)` as special 61 # cases so we can transpile them, since they're relatively common 62 if kind == exp.DataType.Type.TIMESTAMP: 63 if not safe and (int_value or int_scale_or_fmt): 64 # TRY_TO_TIMESTAMP('integer') is not parsed into exp.UnixToTime as 65 # it's not easily transpilable 66 return exp.UnixToTime(this=value, scale=scale_or_fmt) 67 if not int_scale_or_fmt and not is_float(value.name): 68 expr = build_formatted_time(exp.StrToTime, "snowflake")(args) 69 expr.set("safe", safe) 70 return expr 71 72 if kind in (exp.DataType.Type.DATE, exp.DataType.Type.TIME) and not int_value: 73 klass = exp.TsOrDsToDate if kind == exp.DataType.Type.DATE else exp.TsOrDsToTime 74 formatted_exp = build_formatted_time(klass, "snowflake")(args) 75 formatted_exp.set("safe", safe) 76 return formatted_exp 77 78 return exp.Anonymous(this=name, expressions=args) 79 80 return _builder 81 82 83def _build_object_construct(args: t.List) -> t.Union[exp.StarMap, exp.Struct]: 84 expression = parser.build_var_map(args) 85 86 if isinstance(expression, exp.StarMap): 87 return expression 88 89 return exp.Struct( 90 expressions=[ 91 exp.PropertyEQ(this=k, expression=v) for k, v in zip(expression.keys, expression.values) 92 ] 93 ) 94 95 96def _build_datediff(args: t.List) -> exp.DateDiff: 97 return exp.DateDiff( 98 this=seq_get(args, 2), expression=seq_get(args, 1), unit=map_date_part(seq_get(args, 0)) 99 ) 100 101 102def _build_date_time_add(expr_type: t.Type[E]) -> t.Callable[[t.List], E]: 103 def _builder(args: t.List) -> E: 104 return expr_type( 105 this=seq_get(args, 2), 106 expression=seq_get(args, 1), 107 unit=map_date_part(seq_get(args, 0)), 108 ) 109 110 return _builder 111 112 113def _build_bitwise(expr_type: t.Type[B], name: str) -> t.Callable[[t.List], B | exp.Anonymous]: 114 def _builder(args: t.List) -> B | exp.Anonymous: 115 if len(args) == 3: 116 return exp.Anonymous(this=name, expressions=args) 117 118 return binary_from_function(expr_type)(args) 119 120 return _builder 121 122 123# https://docs.snowflake.com/en/sql-reference/functions/div0 124def _build_if_from_div0(args: t.List) -> exp.If: 125 lhs = exp._wrap(seq_get(args, 0), exp.Binary) 126 rhs = exp._wrap(seq_get(args, 1), exp.Binary) 127 128 cond = exp.EQ(this=rhs, expression=exp.Literal.number(0)).and_( 129 exp.Is(this=lhs, expression=exp.null()).not_() 130 ) 131 true = exp.Literal.number(0) 132 false = exp.Div(this=lhs, expression=rhs) 133 return exp.If(this=cond, true=true, false=false) 134 135 136# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 137def _build_if_from_zeroifnull(args: t.List) -> exp.If: 138 cond = exp.Is(this=seq_get(args, 0), expression=exp.Null()) 139 return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0)) 140 141 142# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 143def _build_if_from_nullifzero(args: t.List) -> exp.If: 144 cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0)) 145 return exp.If(this=cond, true=exp.Null(), false=seq_get(args, 0)) 146 147 148def _regexpilike_sql(self: Snowflake.Generator, expression: exp.RegexpILike) -> str: 149 flag = expression.text("flag") 150 151 if "i" not in flag: 152 flag += "i" 153 154 return self.func( 155 "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag) 156 ) 157 158 159def _build_regexp_replace(args: t.List) -> exp.RegexpReplace: 160 regexp_replace = exp.RegexpReplace.from_arg_list(args) 161 162 if not regexp_replace.args.get("replacement"): 163 regexp_replace.set("replacement", exp.Literal.string("")) 164 165 return regexp_replace 166 167 168def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[Snowflake.Parser], exp.Show]: 169 def _parse(self: Snowflake.Parser) -> exp.Show: 170 return self._parse_show_snowflake(*args, **kwargs) 171 172 return _parse 173 174 175def _date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc: 176 trunc = date_trunc_to_time(args) 177 trunc.set("unit", map_date_part(trunc.args["unit"])) 178 return trunc 179 180 181def _unqualify_pivot_columns(expression: exp.Expression) -> exp.Expression: 182 """ 183 Snowflake doesn't allow columns referenced in UNPIVOT to be qualified, 184 so we need to unqualify them. Same goes for ANY ORDER BY <column>. 185 186 Example: 187 >>> from sqlglot import parse_one 188 >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))") 189 >>> print(_unqualify_pivot_columns(expr).sql(dialect="snowflake")) 190 SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april)) 191 """ 192 if isinstance(expression, exp.Pivot): 193 if expression.unpivot: 194 expression = transforms.unqualify_columns(expression) 195 else: 196 for field in expression.fields: 197 field_expr = seq_get(field.expressions if field else [], 0) 198 199 if isinstance(field_expr, exp.PivotAny): 200 unqualified_field_expr = transforms.unqualify_columns(field_expr) 201 t.cast(exp.Expression, field).set("expressions", unqualified_field_expr, 0) 202 203 return expression 204 205 206def _flatten_structured_types_unless_iceberg(expression: exp.Expression) -> exp.Expression: 207 assert isinstance(expression, exp.Create) 208 209 def _flatten_structured_type(expression: exp.DataType) -> exp.DataType: 210 if expression.this in exp.DataType.NESTED_TYPES: 211 expression.set("expressions", None) 212 return expression 213 214 props = expression.args.get("properties") 215 if isinstance(expression.this, exp.Schema) and not (props and props.find(exp.IcebergProperty)): 216 for schema_expression in expression.this.expressions: 217 if isinstance(schema_expression, exp.ColumnDef): 218 column_type = schema_expression.kind 219 if isinstance(column_type, exp.DataType): 220 column_type.transform(_flatten_structured_type, copy=False) 221 222 return expression 223 224 225def _unnest_generate_date_array(unnest: exp.Unnest) -> None: 226 generate_date_array = unnest.expressions[0] 227 start = generate_date_array.args.get("start") 228 end = generate_date_array.args.get("end") 229 step = generate_date_array.args.get("step") 230 231 if not start or not end or not isinstance(step, exp.Interval) or step.name != "1": 232 return 233 234 unit = step.args.get("unit") 235 236 unnest_alias = unnest.args.get("alias") 237 if unnest_alias: 238 unnest_alias = unnest_alias.copy() 239 sequence_value_name = seq_get(unnest_alias.columns, 0) or "value" 240 else: 241 sequence_value_name = "value" 242 243 # We'll add the next sequence value to the starting date and project the result 244 date_add = _build_date_time_add(exp.DateAdd)( 245 [unit, exp.cast(sequence_value_name, "int"), exp.cast(start, "date")] 246 ).as_(sequence_value_name) 247 248 # We use DATEDIFF to compute the number of sequence values needed 249 number_sequence = Snowflake.Parser.FUNCTIONS["ARRAY_GENERATE_RANGE"]( 250 [exp.Literal.number(0), _build_datediff([unit, start, end]) + 1] 251 ) 252 253 unnest.set("expressions", [number_sequence]) 254 unnest.replace(exp.select(date_add).from_(unnest.copy()).subquery(unnest_alias)) 255 256 257def _transform_generate_date_array(expression: exp.Expression) -> exp.Expression: 258 if isinstance(expression, exp.Select): 259 for generate_date_array in expression.find_all(exp.GenerateDateArray): 260 parent = generate_date_array.parent 261 262 # If GENERATE_DATE_ARRAY is used directly as an array (e.g passed into ARRAY_LENGTH), the transformed Snowflake 263 # query is the following (it'll be unnested properly on the next iteration due to copy): 264 # SELECT ref(GENERATE_DATE_ARRAY(...)) -> SELECT ref((SELECT ARRAY_AGG(*) FROM UNNEST(GENERATE_DATE_ARRAY(...)))) 265 if not isinstance(parent, exp.Unnest): 266 unnest = exp.Unnest(expressions=[generate_date_array.copy()]) 267 generate_date_array.replace( 268 exp.select(exp.ArrayAgg(this=exp.Star())).from_(unnest).subquery() 269 ) 270 271 if ( 272 isinstance(parent, exp.Unnest) 273 and isinstance(parent.parent, (exp.From, exp.Join)) 274 and len(parent.expressions) == 1 275 ): 276 _unnest_generate_date_array(parent) 277 278 return expression 279 280 281def _build_regexp_extract(expr_type: t.Type[E]) -> t.Callable[[t.List], E]: 282 def _builder(args: t.List) -> E: 283 return expr_type( 284 this=seq_get(args, 0), 285 expression=seq_get(args, 1), 286 position=seq_get(args, 2), 287 occurrence=seq_get(args, 3), 288 parameters=seq_get(args, 4), 289 group=seq_get(args, 5) or exp.Literal.number(0), 290 ) 291 292 return _builder 293 294 295def _regexpextract_sql(self, expression: exp.RegexpExtract | exp.RegexpExtractAll) -> str: 296 # Other dialects don't support all of the following parameters, so we need to 297 # generate default values as necessary to ensure the transpilation is correct 298 group = expression.args.get("group") 299 300 # To avoid generating all these default values, we set group to None if 301 # it's 0 (also default value) which doesn't trigger the following chain 302 if group and group.name == "0": 303 group = None 304 305 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 306 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 307 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 308 309 return self.func( 310 "REGEXP_SUBSTR" if isinstance(expression, exp.RegexpExtract) else "REGEXP_EXTRACT_ALL", 311 expression.this, 312 expression.expression, 313 position, 314 occurrence, 315 parameters, 316 group, 317 ) 318 319 320def _json_extract_value_array_sql( 321 self: Snowflake.Generator, expression: exp.JSONValueArray | exp.JSONExtractArray 322) -> str: 323 json_extract = exp.JSONExtract(this=expression.this, expression=expression.expression) 324 ident = exp.to_identifier("x") 325 326 if isinstance(expression, exp.JSONValueArray): 327 this: exp.Expression = exp.cast(ident, to=exp.DataType.Type.VARCHAR) 328 else: 329 this = exp.ParseJSON(this=f"TO_JSON({ident})") 330 331 transform_lambda = exp.Lambda(expressions=[ident], this=this) 332 333 return self.func("TRANSFORM", json_extract, transform_lambda) 334 335 336class Snowflake(Dialect): 337 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 338 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 339 NULL_ORDERING = "nulls_are_large" 340 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 341 SUPPORTS_USER_DEFINED_TYPES = False 342 SUPPORTS_SEMI_ANTI_JOIN = False 343 PREFER_CTE_ALIAS_COLUMN = True 344 TABLESAMPLE_SIZE_IS_PERCENT = True 345 COPY_PARAMS_ARE_CSV = False 346 ARRAY_AGG_INCLUDES_NULLS = None 347 348 TIME_MAPPING = { 349 "YYYY": "%Y", 350 "yyyy": "%Y", 351 "YY": "%y", 352 "yy": "%y", 353 "MMMM": "%B", 354 "mmmm": "%B", 355 "MON": "%b", 356 "mon": "%b", 357 "MM": "%m", 358 "mm": "%m", 359 "DD": "%d", 360 "dd": "%-d", 361 "DY": "%a", 362 "dy": "%w", 363 "HH24": "%H", 364 "hh24": "%H", 365 "HH12": "%I", 366 "hh12": "%I", 367 "MI": "%M", 368 "mi": "%M", 369 "SS": "%S", 370 "ss": "%S", 371 "FF6": "%f", 372 "ff6": "%f", 373 } 374 375 DATE_PART_MAPPING = { 376 **Dialect.DATE_PART_MAPPING, 377 "ISOWEEK": "WEEKISO", 378 } 379 380 def quote_identifier(self, expression: E, identify: bool = True) -> E: 381 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 382 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 383 if ( 384 isinstance(expression, exp.Identifier) 385 and isinstance(expression.parent, exp.Table) 386 and expression.name.lower() == "dual" 387 ): 388 return expression # type: ignore 389 390 return super().quote_identifier(expression, identify=identify) 391 392 class JSONPathTokenizer(jsonpath.JSONPathTokenizer): 393 SINGLE_TOKENS = jsonpath.JSONPathTokenizer.SINGLE_TOKENS.copy() 394 SINGLE_TOKENS.pop("$") 395 396 class Parser(parser.Parser): 397 IDENTIFY_PIVOT_STRINGS = True 398 DEFAULT_SAMPLING_METHOD = "BERNOULLI" 399 COLON_IS_VARIANT_EXTRACT = True 400 401 ID_VAR_TOKENS = { 402 *parser.Parser.ID_VAR_TOKENS, 403 TokenType.MATCH_CONDITION, 404 } 405 406 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 407 TABLE_ALIAS_TOKENS.discard(TokenType.MATCH_CONDITION) 408 409 COLON_PLACEHOLDER_TOKENS = ID_VAR_TOKENS | {TokenType.NUMBER} 410 411 FUNCTIONS = { 412 **parser.Parser.FUNCTIONS, 413 "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list, 414 "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args), 415 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 416 this=seq_get(args, 1), expression=seq_get(args, 0) 417 ), 418 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 419 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 420 start=seq_get(args, 0), 421 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 422 step=seq_get(args, 2), 423 ), 424 "BITXOR": _build_bitwise(exp.BitwiseXor, "BITXOR"), 425 "BIT_XOR": _build_bitwise(exp.BitwiseXor, "BITXOR"), 426 "BITOR": _build_bitwise(exp.BitwiseOr, "BITOR"), 427 "BIT_OR": _build_bitwise(exp.BitwiseOr, "BITOR"), 428 "BITSHIFTLEFT": _build_bitwise(exp.BitwiseLeftShift, "BITSHIFTLEFT"), 429 "BIT_SHIFTLEFT": _build_bitwise(exp.BitwiseLeftShift, "BIT_SHIFTLEFT"), 430 "BITSHIFTRIGHT": _build_bitwise(exp.BitwiseRightShift, "BITSHIFTRIGHT"), 431 "BIT_SHIFTRIGHT": _build_bitwise(exp.BitwiseRightShift, "BIT_SHIFTRIGHT"), 432 "BOOLXOR": _build_bitwise(exp.Xor, "BOOLXOR"), 433 "DATE": _build_datetime("DATE", exp.DataType.Type.DATE), 434 "DATE_TRUNC": _date_trunc_to_time, 435 "DATEADD": _build_date_time_add(exp.DateAdd), 436 "DATEDIFF": _build_datediff, 437 "DIV0": _build_if_from_div0, 438 "EDITDISTANCE": lambda args: exp.Levenshtein( 439 this=seq_get(args, 0), expression=seq_get(args, 1), max_dist=seq_get(args, 2) 440 ), 441 "FLATTEN": exp.Explode.from_arg_list, 442 "GET_PATH": lambda args, dialect: exp.JSONExtract( 443 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 444 ), 445 "HEX_DECODE_BINARY": exp.Unhex.from_arg_list, 446 "IFF": exp.If.from_arg_list, 447 "LAST_DAY": lambda args: exp.LastDay( 448 this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1)) 449 ), 450 "LEN": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 451 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 452 "NULLIFZERO": _build_if_from_nullifzero, 453 "OBJECT_CONSTRUCT": _build_object_construct, 454 "REGEXP_EXTRACT_ALL": _build_regexp_extract(exp.RegexpExtractAll), 455 "REGEXP_REPLACE": _build_regexp_replace, 456 "REGEXP_SUBSTR": _build_regexp_extract(exp.RegexpExtract), 457 "REGEXP_SUBSTR_ALL": _build_regexp_extract(exp.RegexpExtractAll), 458 "RLIKE": exp.RegexpLike.from_arg_list, 459 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 460 "TABLE": lambda args: exp.TableFromRows(this=seq_get(args, 0)), 461 "TIMEADD": _build_date_time_add(exp.TimeAdd), 462 "TIMEDIFF": _build_datediff, 463 "TIMESTAMPADD": _build_date_time_add(exp.DateAdd), 464 "TIMESTAMPDIFF": _build_datediff, 465 "TIMESTAMPFROMPARTS": build_timestamp_from_parts, 466 "TIMESTAMP_FROM_PARTS": build_timestamp_from_parts, 467 "TIMESTAMPNTZFROMPARTS": build_timestamp_from_parts, 468 "TIMESTAMP_NTZ_FROM_PARTS": build_timestamp_from_parts, 469 "TRY_PARSE_JSON": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True), 470 "TRY_TO_DATE": _build_datetime("TRY_TO_DATE", exp.DataType.Type.DATE, safe=True), 471 "TRY_TO_TIME": _build_datetime("TRY_TO_TIME", exp.DataType.Type.TIME, safe=True), 472 "TRY_TO_TIMESTAMP": _build_datetime( 473 "TRY_TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP, safe=True 474 ), 475 "TO_CHAR": build_timetostr_or_tochar, 476 "TO_DATE": _build_datetime("TO_DATE", exp.DataType.Type.DATE), 477 "TO_NUMBER": lambda args: exp.ToNumber( 478 this=seq_get(args, 0), 479 format=seq_get(args, 1), 480 precision=seq_get(args, 2), 481 scale=seq_get(args, 3), 482 ), 483 "TO_TIME": _build_datetime("TO_TIME", exp.DataType.Type.TIME), 484 "TO_TIMESTAMP": _build_datetime("TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP), 485 "TO_TIMESTAMP_LTZ": _build_datetime("TO_TIMESTAMP_LTZ", exp.DataType.Type.TIMESTAMPLTZ), 486 "TO_TIMESTAMP_NTZ": _build_datetime("TO_TIMESTAMP_NTZ", exp.DataType.Type.TIMESTAMP), 487 "TO_TIMESTAMP_TZ": _build_datetime("TO_TIMESTAMP_TZ", exp.DataType.Type.TIMESTAMPTZ), 488 "TO_VARCHAR": exp.ToChar.from_arg_list, 489 "ZEROIFNULL": _build_if_from_zeroifnull, 490 } 491 492 FUNCTION_PARSERS = { 493 **parser.Parser.FUNCTION_PARSERS, 494 "DATE_PART": lambda self: self._parse_date_part(), 495 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 496 "LISTAGG": lambda self: self._parse_string_agg(), 497 } 498 FUNCTION_PARSERS.pop("TRIM") 499 500 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 501 502 RANGE_PARSERS = { 503 **parser.Parser.RANGE_PARSERS, 504 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 505 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 506 } 507 508 ALTER_PARSERS = { 509 **parser.Parser.ALTER_PARSERS, 510 "UNSET": lambda self: self.expression( 511 exp.Set, 512 tag=self._match_text_seq("TAG"), 513 expressions=self._parse_csv(self._parse_id_var), 514 unset=True, 515 ), 516 } 517 518 STATEMENT_PARSERS = { 519 **parser.Parser.STATEMENT_PARSERS, 520 TokenType.GET: lambda self: self._parse_get(), 521 TokenType.PUT: lambda self: self._parse_put(), 522 TokenType.SHOW: lambda self: self._parse_show(), 523 } 524 525 PROPERTY_PARSERS = { 526 **parser.Parser.PROPERTY_PARSERS, 527 "CREDENTIALS": lambda self: self._parse_credentials_property(), 528 "FILE_FORMAT": lambda self: self._parse_file_format_property(), 529 "LOCATION": lambda self: self._parse_location_property(), 530 "TAG": lambda self: self._parse_tag(), 531 "USING": lambda self: self._match_text_seq("TEMPLATE") 532 and self.expression(exp.UsingTemplateProperty, this=self._parse_statement()), 533 } 534 535 TYPE_CONVERTERS = { 536 # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number 537 exp.DataType.Type.DECIMAL: build_default_decimal_type(precision=38, scale=0), 538 } 539 540 SHOW_PARSERS = { 541 "DATABASES": _show_parser("DATABASES"), 542 "TERSE DATABASES": _show_parser("DATABASES"), 543 "SCHEMAS": _show_parser("SCHEMAS"), 544 "TERSE SCHEMAS": _show_parser("SCHEMAS"), 545 "OBJECTS": _show_parser("OBJECTS"), 546 "TERSE OBJECTS": _show_parser("OBJECTS"), 547 "TABLES": _show_parser("TABLES"), 548 "TERSE TABLES": _show_parser("TABLES"), 549 "VIEWS": _show_parser("VIEWS"), 550 "TERSE VIEWS": _show_parser("VIEWS"), 551 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 552 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 553 "IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 554 "TERSE IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 555 "UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 556 "TERSE UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 557 "SEQUENCES": _show_parser("SEQUENCES"), 558 "TERSE SEQUENCES": _show_parser("SEQUENCES"), 559 "STAGES": _show_parser("STAGES"), 560 "COLUMNS": _show_parser("COLUMNS"), 561 "USERS": _show_parser("USERS"), 562 "TERSE USERS": _show_parser("USERS"), 563 "FILE FORMATS": _show_parser("FILE FORMATS"), 564 "FUNCTIONS": _show_parser("FUNCTIONS"), 565 "PROCEDURES": _show_parser("PROCEDURES"), 566 "WAREHOUSES": _show_parser("WAREHOUSES"), 567 } 568 569 CONSTRAINT_PARSERS = { 570 **parser.Parser.CONSTRAINT_PARSERS, 571 "WITH": lambda self: self._parse_with_constraint(), 572 "MASKING": lambda self: self._parse_with_constraint(), 573 "PROJECTION": lambda self: self._parse_with_constraint(), 574 "TAG": lambda self: self._parse_with_constraint(), 575 } 576 577 STAGED_FILE_SINGLE_TOKENS = { 578 TokenType.DOT, 579 TokenType.MOD, 580 TokenType.SLASH, 581 } 582 583 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 584 585 SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"} 586 587 NON_TABLE_CREATABLES = {"STORAGE INTEGRATION", "TAG", "WAREHOUSE", "STREAMLIT"} 588 589 LAMBDAS = { 590 **parser.Parser.LAMBDAS, 591 TokenType.ARROW: lambda self, expressions: self.expression( 592 exp.Lambda, 593 this=self._replace_lambda( 594 self._parse_assignment(), 595 expressions, 596 ), 597 expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions], 598 ), 599 } 600 601 def _parse_use(self) -> exp.Use: 602 if self._match_text_seq("SECONDARY", "ROLES"): 603 this = self._match_texts(("ALL", "NONE")) and exp.var(self._prev.text.upper()) 604 roles = None if this else self._parse_csv(lambda: self._parse_table(schema=False)) 605 return self.expression( 606 exp.Use, kind="SECONDARY ROLES", this=this, expressions=roles 607 ) 608 609 return super()._parse_use() 610 611 def _negate_range( 612 self, this: t.Optional[exp.Expression] = None 613 ) -> t.Optional[exp.Expression]: 614 if not this: 615 return this 616 617 query = this.args.get("query") 618 if isinstance(this, exp.In) and isinstance(query, exp.Query): 619 # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so 620 # we do this conversion here to avoid parsing it into `NOT value IN (subquery)` 621 # which can produce different results (most likely a SnowFlake bug). 622 # 623 # https://docs.snowflake.com/en/sql-reference/functions/in 624 # Context: https://github.com/tobymao/sqlglot/issues/3890 625 return self.expression( 626 exp.NEQ, this=this.this, expression=exp.All(this=query.unnest()) 627 ) 628 629 return self.expression(exp.Not, this=this) 630 631 def _parse_tag(self) -> exp.Tags: 632 return self.expression( 633 exp.Tags, 634 expressions=self._parse_wrapped_csv(self._parse_property), 635 ) 636 637 def _parse_with_constraint(self) -> t.Optional[exp.Expression]: 638 if self._prev.token_type != TokenType.WITH: 639 self._retreat(self._index - 1) 640 641 if self._match_text_seq("MASKING", "POLICY"): 642 policy = self._parse_column() 643 return self.expression( 644 exp.MaskingPolicyColumnConstraint, 645 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 646 expressions=self._match(TokenType.USING) 647 and self._parse_wrapped_csv(self._parse_id_var), 648 ) 649 if self._match_text_seq("PROJECTION", "POLICY"): 650 policy = self._parse_column() 651 return self.expression( 652 exp.ProjectionPolicyColumnConstraint, 653 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 654 ) 655 if self._match(TokenType.TAG): 656 return self._parse_tag() 657 658 return None 659 660 def _parse_with_property(self) -> t.Optional[exp.Expression] | t.List[exp.Expression]: 661 if self._match(TokenType.TAG): 662 return self._parse_tag() 663 664 return super()._parse_with_property() 665 666 def _parse_create(self) -> exp.Create | exp.Command: 667 expression = super()._parse_create() 668 if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES: 669 # Replace the Table node with the enclosed Identifier 670 expression.this.replace(expression.this.this) 671 672 return expression 673 674 # https://docs.snowflake.com/en/sql-reference/functions/date_part.html 675 # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 676 def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 677 this = self._parse_var() or self._parse_type() 678 679 if not this: 680 return None 681 682 self._match(TokenType.COMMA) 683 expression = self._parse_bitwise() 684 this = map_date_part(this) 685 name = this.name.upper() 686 687 if name.startswith("EPOCH"): 688 if name == "EPOCH_MILLISECOND": 689 scale = 10**3 690 elif name == "EPOCH_MICROSECOND": 691 scale = 10**6 692 elif name == "EPOCH_NANOSECOND": 693 scale = 10**9 694 else: 695 scale = None 696 697 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 698 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 699 700 if scale: 701 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 702 703 return to_unix 704 705 return self.expression(exp.Extract, this=this, expression=expression) 706 707 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 708 if is_map: 709 # Keys are strings in Snowflake's objects, see also: 710 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 711 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 712 return self._parse_slice(self._parse_string()) 713 714 return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True)) 715 716 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 717 lateral = super()._parse_lateral() 718 if not lateral: 719 return lateral 720 721 if isinstance(lateral.this, exp.Explode): 722 table_alias = lateral.args.get("alias") 723 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 724 if table_alias and not table_alias.args.get("columns"): 725 table_alias.set("columns", columns) 726 elif not table_alias: 727 exp.alias_(lateral, "_flattened", table=columns, copy=False) 728 729 return lateral 730 731 def _parse_table_parts( 732 self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False 733 ) -> exp.Table: 734 # https://docs.snowflake.com/en/user-guide/querying-stage 735 if self._match(TokenType.STRING, advance=False): 736 table = self._parse_string() 737 elif self._match_text_seq("@", advance=False): 738 table = self._parse_location_path() 739 else: 740 table = None 741 742 if table: 743 file_format = None 744 pattern = None 745 746 wrapped = self._match(TokenType.L_PAREN) 747 while self._curr and wrapped and not self._match(TokenType.R_PAREN): 748 if self._match_text_seq("FILE_FORMAT", "=>"): 749 file_format = self._parse_string() or super()._parse_table_parts( 750 is_db_reference=is_db_reference 751 ) 752 elif self._match_text_seq("PATTERN", "=>"): 753 pattern = self._parse_string() 754 else: 755 break 756 757 self._match(TokenType.COMMA) 758 759 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 760 else: 761 table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 762 763 return table 764 765 def _parse_table( 766 self, 767 schema: bool = False, 768 joins: bool = False, 769 alias_tokens: t.Optional[t.Collection[TokenType]] = None, 770 parse_bracket: bool = False, 771 is_db_reference: bool = False, 772 parse_partition: bool = False, 773 ) -> t.Optional[exp.Expression]: 774 table = super()._parse_table( 775 schema=schema, 776 joins=joins, 777 alias_tokens=alias_tokens, 778 parse_bracket=parse_bracket, 779 is_db_reference=is_db_reference, 780 parse_partition=parse_partition, 781 ) 782 if isinstance(table, exp.Table) and isinstance(table.this, exp.TableFromRows): 783 table_from_rows = table.this 784 for arg in exp.TableFromRows.arg_types: 785 if arg != "this": 786 table_from_rows.set(arg, table.args.get(arg)) 787 788 table = table_from_rows 789 790 return table 791 792 def _parse_id_var( 793 self, 794 any_token: bool = True, 795 tokens: t.Optional[t.Collection[TokenType]] = None, 796 ) -> t.Optional[exp.Expression]: 797 if self._match_text_seq("IDENTIFIER", "("): 798 identifier = ( 799 super()._parse_id_var(any_token=any_token, tokens=tokens) 800 or self._parse_string() 801 ) 802 self._match_r_paren() 803 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 804 805 return super()._parse_id_var(any_token=any_token, tokens=tokens) 806 807 def _parse_show_snowflake(self, this: str) -> exp.Show: 808 scope = None 809 scope_kind = None 810 811 # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS 812 # which is syntactically valid but has no effect on the output 813 terse = self._tokens[self._index - 2].text.upper() == "TERSE" 814 815 history = self._match_text_seq("HISTORY") 816 817 like = self._parse_string() if self._match(TokenType.LIKE) else None 818 819 if self._match(TokenType.IN): 820 if self._match_text_seq("ACCOUNT"): 821 scope_kind = "ACCOUNT" 822 elif self._match_text_seq("CLASS"): 823 scope_kind = "CLASS" 824 scope = self._parse_table_parts() 825 elif self._match_text_seq("APPLICATION"): 826 scope_kind = "APPLICATION" 827 if self._match_text_seq("PACKAGE"): 828 scope_kind += " PACKAGE" 829 scope = self._parse_table_parts() 830 elif self._match_set(self.DB_CREATABLES): 831 scope_kind = self._prev.text.upper() 832 if self._curr: 833 scope = self._parse_table_parts() 834 elif self._curr: 835 scope_kind = "SCHEMA" if this in self.SCHEMA_KINDS else "TABLE" 836 scope = self._parse_table_parts() 837 838 return self.expression( 839 exp.Show, 840 **{ 841 "terse": terse, 842 "this": this, 843 "history": history, 844 "like": like, 845 "scope": scope, 846 "scope_kind": scope_kind, 847 "starts_with": self._match_text_seq("STARTS", "WITH") and self._parse_string(), 848 "limit": self._parse_limit(), 849 "from": self._parse_string() if self._match(TokenType.FROM) else None, 850 "privileges": self._match_text_seq("WITH", "PRIVILEGES") 851 and self._parse_csv(lambda: self._parse_var(any_token=True, upper=True)), 852 }, 853 ) 854 855 def _parse_put(self) -> exp.Put | exp.Command: 856 if self._curr.token_type != TokenType.STRING: 857 return self._parse_as_command(self._prev) 858 859 return self.expression( 860 exp.Put, 861 this=self._parse_string(), 862 target=self._parse_location_path(), 863 properties=self._parse_properties(), 864 ) 865 866 def _parse_get(self) -> t.Optional[exp.Expression]: 867 start = self._prev 868 869 # If we detect GET( then we need to parse a function, not a statement 870 if self._match(TokenType.L_PAREN): 871 self._retreat(self._index - 2) 872 return self._parse_expression() 873 874 target = self._parse_location_path() 875 876 # Parse as command if unquoted file path 877 if self._curr.token_type == TokenType.URI_START: 878 return self._parse_as_command(start) 879 880 return self.expression( 881 exp.Get, 882 this=self._parse_string(), 883 target=target, 884 properties=self._parse_properties(), 885 ) 886 887 def _parse_location_property(self) -> exp.LocationProperty: 888 self._match(TokenType.EQ) 889 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 890 891 def _parse_file_location(self) -> t.Optional[exp.Expression]: 892 # Parse either a subquery or a staged file 893 return ( 894 self._parse_select(table=True, parse_subquery_alias=False) 895 if self._match(TokenType.L_PAREN, advance=False) 896 else self._parse_table_parts() 897 ) 898 899 def _parse_location_path(self) -> exp.Var: 900 start = self._curr 901 self._advance_any(ignore_reserved=True) 902 903 # We avoid consuming a comma token because external tables like @foo and @bar 904 # can be joined in a query with a comma separator, as well as closing paren 905 # in case of subqueries 906 while self._is_connected() and not self._match_set( 907 (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False 908 ): 909 self._advance_any(ignore_reserved=True) 910 911 return exp.var(self._find_sql(start, self._prev)) 912 913 def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: 914 this = super()._parse_lambda_arg() 915 916 if not this: 917 return this 918 919 typ = self._parse_types() 920 921 if typ: 922 return self.expression(exp.Cast, this=this, to=typ) 923 924 return this 925 926 def _parse_foreign_key(self) -> exp.ForeignKey: 927 # inlineFK, the REFERENCES columns are implied 928 if self._match(TokenType.REFERENCES, advance=False): 929 return self.expression(exp.ForeignKey) 930 931 # outoflineFK, explicitly names the columns 932 return super()._parse_foreign_key() 933 934 def _parse_file_format_property(self) -> exp.FileFormatProperty: 935 self._match(TokenType.EQ) 936 if self._match(TokenType.L_PAREN, advance=False): 937 expressions = self._parse_wrapped_options() 938 else: 939 expressions = [self._parse_format_name()] 940 941 return self.expression( 942 exp.FileFormatProperty, 943 expressions=expressions, 944 ) 945 946 def _parse_credentials_property(self) -> exp.CredentialsProperty: 947 return self.expression( 948 exp.CredentialsProperty, 949 expressions=self._parse_wrapped_options(), 950 ) 951 952 class Tokenizer(tokens.Tokenizer): 953 STRING_ESCAPES = ["\\", "'"] 954 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 955 RAW_STRINGS = ["$$"] 956 COMMENTS = ["--", "//", ("/*", "*/")] 957 NESTED_COMMENTS = False 958 959 KEYWORDS = { 960 **tokens.Tokenizer.KEYWORDS, 961 "FILE://": TokenType.URI_START, 962 "BYTEINT": TokenType.INT, 963 "EXCLUDE": TokenType.EXCEPT, 964 "FILE FORMAT": TokenType.FILE_FORMAT, 965 "GET": TokenType.GET, 966 "ILIKE ANY": TokenType.ILIKE_ANY, 967 "LIKE ANY": TokenType.LIKE_ANY, 968 "MATCH_CONDITION": TokenType.MATCH_CONDITION, 969 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 970 "MINUS": TokenType.EXCEPT, 971 "NCHAR VARYING": TokenType.VARCHAR, 972 "PUT": TokenType.PUT, 973 "REMOVE": TokenType.COMMAND, 974 "RM": TokenType.COMMAND, 975 "SAMPLE": TokenType.TABLE_SAMPLE, 976 "SQL_DOUBLE": TokenType.DOUBLE, 977 "SQL_VARCHAR": TokenType.VARCHAR, 978 "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION, 979 "TAG": TokenType.TAG, 980 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 981 "TOP": TokenType.TOP, 982 "WAREHOUSE": TokenType.WAREHOUSE, 983 "STAGE": TokenType.STAGE, 984 "STREAMLIT": TokenType.STREAMLIT, 985 } 986 KEYWORDS.pop("/*+") 987 988 SINGLE_TOKENS = { 989 **tokens.Tokenizer.SINGLE_TOKENS, 990 "$": TokenType.PARAMETER, 991 } 992 993 VAR_SINGLE_TOKENS = {"$"} 994 995 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 996 997 class Generator(generator.Generator): 998 PARAMETER_TOKEN = "$" 999 MATCHED_BY_SOURCE = False 1000 SINGLE_STRING_INTERVAL = True 1001 JOIN_HINTS = False 1002 TABLE_HINTS = False 1003 QUERY_HINTS = False 1004 AGGREGATE_FILTER_SUPPORTED = False 1005 SUPPORTS_TABLE_COPY = False 1006 COLLATE_IS_FUNC = True 1007 LIMIT_ONLY_LITERALS = True 1008 JSON_KEY_VALUE_PAIR_SEP = "," 1009 INSERT_OVERWRITE = " OVERWRITE INTO" 1010 STRUCT_DELIMITER = ("(", ")") 1011 COPY_PARAMS_ARE_WRAPPED = False 1012 COPY_PARAMS_EQ_REQUIRED = True 1013 STAR_EXCEPT = "EXCLUDE" 1014 SUPPORTS_EXPLODING_PROJECTIONS = False 1015 ARRAY_CONCAT_IS_VAR_LEN = False 1016 SUPPORTS_CONVERT_TIMEZONE = True 1017 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 1018 SUPPORTS_MEDIAN = True 1019 ARRAY_SIZE_NAME = "ARRAY_SIZE" 1020 1021 TRANSFORMS = { 1022 **generator.Generator.TRANSFORMS, 1023 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 1024 exp.ArgMax: rename_func("MAX_BY"), 1025 exp.ArgMin: rename_func("MIN_BY"), 1026 exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"), 1027 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 1028 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 1029 exp.AtTimeZone: lambda self, e: self.func( 1030 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 1031 ), 1032 exp.BitwiseOr: rename_func("BITOR"), 1033 exp.BitwiseXor: rename_func("BITXOR"), 1034 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 1035 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 1036 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 1037 exp.DateAdd: date_delta_sql("DATEADD"), 1038 exp.DateDiff: date_delta_sql("DATEDIFF"), 1039 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 1040 exp.DatetimeDiff: timestampdiff_sql, 1041 exp.DateStrToDate: datestrtodate_sql, 1042 exp.DayOfMonth: rename_func("DAYOFMONTH"), 1043 exp.DayOfWeek: rename_func("DAYOFWEEK"), 1044 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 1045 exp.DayOfYear: rename_func("DAYOFYEAR"), 1046 exp.Explode: rename_func("FLATTEN"), 1047 exp.Extract: lambda self, e: self.func( 1048 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 1049 ), 1050 exp.FileFormatProperty: lambda self, 1051 e: f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})", 1052 exp.FromTimeZone: lambda self, e: self.func( 1053 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 1054 ), 1055 exp.GenerateSeries: lambda self, e: self.func( 1056 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 1057 ), 1058 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 1059 exp.If: if_sql(name="IFF", false_value="NULL"), 1060 exp.JSONExtractArray: _json_extract_value_array_sql, 1061 exp.JSONExtractScalar: lambda self, e: self.func( 1062 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 1063 ), 1064 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 1065 exp.JSONPathRoot: lambda *_: "", 1066 exp.JSONValueArray: _json_extract_value_array_sql, 1067 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 1068 rename_func("EDITDISTANCE") 1069 ), 1070 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 1071 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 1072 exp.LogicalOr: rename_func("BOOLOR_AGG"), 1073 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 1074 exp.MakeInterval: no_make_interval_sql, 1075 exp.Max: max_or_greatest, 1076 exp.Min: min_or_least, 1077 exp.ParseJSON: lambda self, e: self.func( 1078 "TRY_PARSE_JSON" if e.args.get("safe") else "PARSE_JSON", e.this 1079 ), 1080 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 1081 exp.PercentileCont: transforms.preprocess( 1082 [transforms.add_within_group_for_percentiles] 1083 ), 1084 exp.PercentileDisc: transforms.preprocess( 1085 [transforms.add_within_group_for_percentiles] 1086 ), 1087 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 1088 exp.RegexpExtract: _regexpextract_sql, 1089 exp.RegexpExtractAll: _regexpextract_sql, 1090 exp.RegexpILike: _regexpilike_sql, 1091 exp.Rand: rename_func("RANDOM"), 1092 exp.Select: transforms.preprocess( 1093 [ 1094 transforms.eliminate_window_clause, 1095 transforms.eliminate_distinct_on, 1096 transforms.explode_projection_to_unnest(), 1097 transforms.eliminate_semi_and_anti_joins, 1098 _transform_generate_date_array, 1099 ] 1100 ), 1101 exp.SHA: rename_func("SHA1"), 1102 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 1103 exp.StartsWith: rename_func("STARTSWITH"), 1104 exp.EndsWith: rename_func("ENDSWITH"), 1105 exp.StrPosition: lambda self, e: strposition_sql( 1106 self, e, func_name="CHARINDEX", supports_position=True 1107 ), 1108 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 1109 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 1110 exp.Stuff: rename_func("INSERT"), 1111 exp.StPoint: rename_func("ST_MAKEPOINT"), 1112 exp.TimeAdd: date_delta_sql("TIMEADD"), 1113 exp.Timestamp: no_timestamp_sql, 1114 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 1115 exp.TimestampDiff: lambda self, e: self.func( 1116 "TIMESTAMPDIFF", e.unit, e.expression, e.this 1117 ), 1118 exp.TimestampTrunc: timestamptrunc_sql(), 1119 exp.TimeStrToTime: timestrtotime_sql, 1120 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 1121 exp.ToArray: rename_func("TO_ARRAY"), 1122 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 1123 exp.ToDouble: rename_func("TO_DOUBLE"), 1124 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 1125 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 1126 exp.TsOrDsToDate: lambda self, e: self.func( 1127 "TRY_TO_DATE" if e.args.get("safe") else "TO_DATE", e.this, self.format_time(e) 1128 ), 1129 exp.TsOrDsToTime: lambda self, e: self.func( 1130 "TRY_TO_TIME" if e.args.get("safe") else "TO_TIME", e.this, self.format_time(e) 1131 ), 1132 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 1133 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 1134 exp.Uuid: rename_func("UUID_STRING"), 1135 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 1136 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 1137 exp.Xor: rename_func("BOOLXOR"), 1138 } 1139 1140 SUPPORTED_JSON_PATH_PARTS = { 1141 exp.JSONPathKey, 1142 exp.JSONPathRoot, 1143 exp.JSONPathSubscript, 1144 } 1145 1146 TYPE_MAPPING = { 1147 **generator.Generator.TYPE_MAPPING, 1148 exp.DataType.Type.NESTED: "OBJECT", 1149 exp.DataType.Type.STRUCT: "OBJECT", 1150 exp.DataType.Type.BIGDECIMAL: "DOUBLE", 1151 } 1152 1153 TOKEN_MAPPING = { 1154 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 1155 } 1156 1157 PROPERTIES_LOCATION = { 1158 **generator.Generator.PROPERTIES_LOCATION, 1159 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 1160 exp.LocationProperty: exp.Properties.Location.POST_WITH, 1161 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 1162 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 1163 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 1164 } 1165 1166 UNSUPPORTED_VALUES_EXPRESSIONS = { 1167 exp.Map, 1168 exp.StarMap, 1169 exp.Struct, 1170 exp.VarMap, 1171 } 1172 1173 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 1174 1175 def with_properties(self, properties: exp.Properties) -> str: 1176 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 1177 1178 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 1179 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 1180 values_as_table = False 1181 1182 return super().values_sql(expression, values_as_table=values_as_table) 1183 1184 def datatype_sql(self, expression: exp.DataType) -> str: 1185 expressions = expression.expressions 1186 if ( 1187 expressions 1188 and expression.is_type(*exp.DataType.STRUCT_TYPES) 1189 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 1190 ): 1191 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 1192 return "OBJECT" 1193 1194 return super().datatype_sql(expression) 1195 1196 def tonumber_sql(self, expression: exp.ToNumber) -> str: 1197 return self.func( 1198 "TO_NUMBER", 1199 expression.this, 1200 expression.args.get("format"), 1201 expression.args.get("precision"), 1202 expression.args.get("scale"), 1203 ) 1204 1205 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 1206 milli = expression.args.get("milli") 1207 if milli is not None: 1208 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 1209 expression.set("nano", milli_to_nano) 1210 1211 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 1212 1213 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1214 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 1215 return self.func("TO_GEOGRAPHY", expression.this) 1216 if expression.is_type(exp.DataType.Type.GEOMETRY): 1217 return self.func("TO_GEOMETRY", expression.this) 1218 1219 return super().cast_sql(expression, safe_prefix=safe_prefix) 1220 1221 def trycast_sql(self, expression: exp.TryCast) -> str: 1222 value = expression.this 1223 1224 if value.type is None: 1225 from sqlglot.optimizer.annotate_types import annotate_types 1226 1227 value = annotate_types(value, dialect=self.dialect) 1228 1229 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 1230 return super().trycast_sql(expression) 1231 1232 # TRY_CAST only works for string values in Snowflake 1233 return self.cast_sql(expression) 1234 1235 def log_sql(self, expression: exp.Log) -> str: 1236 if not expression.expression: 1237 return self.func("LN", expression.this) 1238 1239 return super().log_sql(expression) 1240 1241 def unnest_sql(self, expression: exp.Unnest) -> str: 1242 unnest_alias = expression.args.get("alias") 1243 offset = expression.args.get("offset") 1244 1245 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 1246 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 1247 1248 columns = [ 1249 exp.to_identifier("seq"), 1250 exp.to_identifier("key"), 1251 exp.to_identifier("path"), 1252 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 1253 value, 1254 exp.to_identifier("this"), 1255 ] 1256 1257 if unnest_alias: 1258 unnest_alias.set("columns", columns) 1259 else: 1260 unnest_alias = exp.TableAlias(this="_u", columns=columns) 1261 1262 table_input = self.sql(expression.expressions[0]) 1263 if not table_input.startswith("INPUT =>"): 1264 table_input = f"INPUT => {table_input}" 1265 1266 explode = f"TABLE(FLATTEN({table_input}))" 1267 alias = self.sql(unnest_alias) 1268 alias = f" AS {alias}" if alias else "" 1269 value = "" if isinstance(expression.parent, (exp.From, exp.Join)) else f"{value} FROM " 1270 1271 return f"{value}{explode}{alias}" 1272 1273 def show_sql(self, expression: exp.Show) -> str: 1274 terse = "TERSE " if expression.args.get("terse") else "" 1275 history = " HISTORY" if expression.args.get("history") else "" 1276 like = self.sql(expression, "like") 1277 like = f" LIKE {like}" if like else "" 1278 1279 scope = self.sql(expression, "scope") 1280 scope = f" {scope}" if scope else "" 1281 1282 scope_kind = self.sql(expression, "scope_kind") 1283 if scope_kind: 1284 scope_kind = f" IN {scope_kind}" 1285 1286 starts_with = self.sql(expression, "starts_with") 1287 if starts_with: 1288 starts_with = f" STARTS WITH {starts_with}" 1289 1290 limit = self.sql(expression, "limit") 1291 1292 from_ = self.sql(expression, "from") 1293 if from_: 1294 from_ = f" FROM {from_}" 1295 1296 privileges = self.expressions(expression, key="privileges", flat=True) 1297 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 1298 1299 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 1300 1301 def describe_sql(self, expression: exp.Describe) -> str: 1302 # Default to table if kind is unknown 1303 kind_value = expression.args.get("kind") or "TABLE" 1304 kind = f" {kind_value}" if kind_value else "" 1305 this = f" {self.sql(expression, 'this')}" 1306 expressions = self.expressions(expression, flat=True) 1307 expressions = f" {expressions}" if expressions else "" 1308 return f"DESCRIBE{kind}{this}{expressions}" 1309 1310 def generatedasidentitycolumnconstraint_sql( 1311 self, expression: exp.GeneratedAsIdentityColumnConstraint 1312 ) -> str: 1313 start = expression.args.get("start") 1314 start = f" START {start}" if start else "" 1315 increment = expression.args.get("increment") 1316 increment = f" INCREMENT {increment}" if increment else "" 1317 return f"AUTOINCREMENT{start}{increment}" 1318 1319 def cluster_sql(self, expression: exp.Cluster) -> str: 1320 return f"CLUSTER BY ({self.expressions(expression, flat=True)})" 1321 1322 def struct_sql(self, expression: exp.Struct) -> str: 1323 keys = [] 1324 values = [] 1325 1326 for i, e in enumerate(expression.expressions): 1327 if isinstance(e, exp.PropertyEQ): 1328 keys.append( 1329 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1330 ) 1331 values.append(e.expression) 1332 else: 1333 keys.append(exp.Literal.string(f"_{i}")) 1334 values.append(e) 1335 1336 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 1337 1338 @unsupported_args("weight", "accuracy") 1339 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 1340 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 1341 1342 def alterset_sql(self, expression: exp.AlterSet) -> str: 1343 exprs = self.expressions(expression, flat=True) 1344 exprs = f" {exprs}" if exprs else "" 1345 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1346 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1347 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1348 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1349 tag = self.expressions(expression, key="tag", flat=True) 1350 tag = f" TAG {tag}" if tag else "" 1351 1352 return f"SET{exprs}{file_format}{copy_options}{tag}" 1353 1354 def strtotime_sql(self, expression: exp.StrToTime): 1355 safe_prefix = "TRY_" if expression.args.get("safe") else "" 1356 return self.func( 1357 f"{safe_prefix}TO_TIMESTAMP", expression.this, self.format_time(expression) 1358 ) 1359 1360 def timestampsub_sql(self, expression: exp.TimestampSub): 1361 return self.sql( 1362 exp.TimestampAdd( 1363 this=expression.this, 1364 expression=expression.expression * -1, 1365 unit=expression.unit, 1366 ) 1367 ) 1368 1369 def jsonextract_sql(self, expression: exp.JSONExtract): 1370 this = expression.this 1371 1372 # JSON strings are valid coming from other dialects such as BQ 1373 return self.func( 1374 "GET_PATH", 1375 exp.ParseJSON(this=this) if this.is_string else this, 1376 expression.expression, 1377 ) 1378 1379 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 1380 this = expression.this 1381 if not isinstance(this, exp.TsOrDsToTimestamp): 1382 this = exp.cast(this, exp.DataType.Type.TIMESTAMP) 1383 1384 return self.func("TO_CHAR", this, self.format_time(expression)) 1385 1386 def datesub_sql(self, expression: exp.DateSub) -> str: 1387 value = expression.expression 1388 if value: 1389 value.replace(value * (-1)) 1390 else: 1391 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1392 1393 return date_delta_sql("DATEADD")(self, expression) 1394 1395 def select_sql(self, expression: exp.Select) -> str: 1396 limit = expression.args.get("limit") 1397 offset = expression.args.get("offset") 1398 if offset and not limit: 1399 expression.limit(exp.Null(), copy=False) 1400 return super().select_sql(expression) 1401 1402 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1403 is_materialized = expression.find(exp.MaterializedProperty) 1404 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1405 1406 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1407 # For materialized views, COPY GRANTS is located *before* the columns list 1408 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1409 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1410 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1411 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1412 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1413 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1414 1415 this_name = self.sql(expression.this, "this") 1416 copy_grants = self.sql(copy_grants_property) 1417 this_schema = self.schema_columns_sql(expression.this) 1418 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1419 1420 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1421 1422 return super().createable_sql(expression, locations) 1423 1424 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1425 this = expression.this 1426 1427 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1428 # and add it later as part of the WITHIN GROUP clause 1429 order = this if isinstance(this, exp.Order) else None 1430 if order: 1431 expression.set("this", order.this.pop()) 1432 1433 expr_sql = super().arrayagg_sql(expression) 1434 1435 if order: 1436 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1437 1438 return expr_sql 1439 1440 def array_sql(self, expression: exp.Array) -> str: 1441 expressions = expression.expressions 1442 1443 first_expr = seq_get(expressions, 0) 1444 if isinstance(first_expr, exp.Select): 1445 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1446 if first_expr.text("kind").upper() == "STRUCT": 1447 object_construct_args = [] 1448 for expr in first_expr.expressions: 1449 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1450 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1451 name = expr.this if isinstance(expr, exp.Alias) else expr 1452 1453 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1454 1455 array_agg = exp.ArrayAgg( 1456 this=_build_object_construct(args=object_construct_args) 1457 ) 1458 1459 first_expr.set("kind", None) 1460 first_expr.set("expressions", [array_agg]) 1461 1462 return self.sql(first_expr.subquery()) 1463 1464 return inline_array_sql(self, expression)
337class Snowflake(Dialect): 338 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 339 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 340 NULL_ORDERING = "nulls_are_large" 341 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 342 SUPPORTS_USER_DEFINED_TYPES = False 343 SUPPORTS_SEMI_ANTI_JOIN = False 344 PREFER_CTE_ALIAS_COLUMN = True 345 TABLESAMPLE_SIZE_IS_PERCENT = True 346 COPY_PARAMS_ARE_CSV = False 347 ARRAY_AGG_INCLUDES_NULLS = None 348 349 TIME_MAPPING = { 350 "YYYY": "%Y", 351 "yyyy": "%Y", 352 "YY": "%y", 353 "yy": "%y", 354 "MMMM": "%B", 355 "mmmm": "%B", 356 "MON": "%b", 357 "mon": "%b", 358 "MM": "%m", 359 "mm": "%m", 360 "DD": "%d", 361 "dd": "%-d", 362 "DY": "%a", 363 "dy": "%w", 364 "HH24": "%H", 365 "hh24": "%H", 366 "HH12": "%I", 367 "hh12": "%I", 368 "MI": "%M", 369 "mi": "%M", 370 "SS": "%S", 371 "ss": "%S", 372 "FF6": "%f", 373 "ff6": "%f", 374 } 375 376 DATE_PART_MAPPING = { 377 **Dialect.DATE_PART_MAPPING, 378 "ISOWEEK": "WEEKISO", 379 } 380 381 def quote_identifier(self, expression: E, identify: bool = True) -> E: 382 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 383 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 384 if ( 385 isinstance(expression, exp.Identifier) 386 and isinstance(expression.parent, exp.Table) 387 and expression.name.lower() == "dual" 388 ): 389 return expression # type: ignore 390 391 return super().quote_identifier(expression, identify=identify) 392 393 class JSONPathTokenizer(jsonpath.JSONPathTokenizer): 394 SINGLE_TOKENS = jsonpath.JSONPathTokenizer.SINGLE_TOKENS.copy() 395 SINGLE_TOKENS.pop("$") 396 397 class Parser(parser.Parser): 398 IDENTIFY_PIVOT_STRINGS = True 399 DEFAULT_SAMPLING_METHOD = "BERNOULLI" 400 COLON_IS_VARIANT_EXTRACT = True 401 402 ID_VAR_TOKENS = { 403 *parser.Parser.ID_VAR_TOKENS, 404 TokenType.MATCH_CONDITION, 405 } 406 407 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 408 TABLE_ALIAS_TOKENS.discard(TokenType.MATCH_CONDITION) 409 410 COLON_PLACEHOLDER_TOKENS = ID_VAR_TOKENS | {TokenType.NUMBER} 411 412 FUNCTIONS = { 413 **parser.Parser.FUNCTIONS, 414 "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list, 415 "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args), 416 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 417 this=seq_get(args, 1), expression=seq_get(args, 0) 418 ), 419 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 420 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 421 start=seq_get(args, 0), 422 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 423 step=seq_get(args, 2), 424 ), 425 "BITXOR": _build_bitwise(exp.BitwiseXor, "BITXOR"), 426 "BIT_XOR": _build_bitwise(exp.BitwiseXor, "BITXOR"), 427 "BITOR": _build_bitwise(exp.BitwiseOr, "BITOR"), 428 "BIT_OR": _build_bitwise(exp.BitwiseOr, "BITOR"), 429 "BITSHIFTLEFT": _build_bitwise(exp.BitwiseLeftShift, "BITSHIFTLEFT"), 430 "BIT_SHIFTLEFT": _build_bitwise(exp.BitwiseLeftShift, "BIT_SHIFTLEFT"), 431 "BITSHIFTRIGHT": _build_bitwise(exp.BitwiseRightShift, "BITSHIFTRIGHT"), 432 "BIT_SHIFTRIGHT": _build_bitwise(exp.BitwiseRightShift, "BIT_SHIFTRIGHT"), 433 "BOOLXOR": _build_bitwise(exp.Xor, "BOOLXOR"), 434 "DATE": _build_datetime("DATE", exp.DataType.Type.DATE), 435 "DATE_TRUNC": _date_trunc_to_time, 436 "DATEADD": _build_date_time_add(exp.DateAdd), 437 "DATEDIFF": _build_datediff, 438 "DIV0": _build_if_from_div0, 439 "EDITDISTANCE": lambda args: exp.Levenshtein( 440 this=seq_get(args, 0), expression=seq_get(args, 1), max_dist=seq_get(args, 2) 441 ), 442 "FLATTEN": exp.Explode.from_arg_list, 443 "GET_PATH": lambda args, dialect: exp.JSONExtract( 444 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 445 ), 446 "HEX_DECODE_BINARY": exp.Unhex.from_arg_list, 447 "IFF": exp.If.from_arg_list, 448 "LAST_DAY": lambda args: exp.LastDay( 449 this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1)) 450 ), 451 "LEN": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 452 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 453 "NULLIFZERO": _build_if_from_nullifzero, 454 "OBJECT_CONSTRUCT": _build_object_construct, 455 "REGEXP_EXTRACT_ALL": _build_regexp_extract(exp.RegexpExtractAll), 456 "REGEXP_REPLACE": _build_regexp_replace, 457 "REGEXP_SUBSTR": _build_regexp_extract(exp.RegexpExtract), 458 "REGEXP_SUBSTR_ALL": _build_regexp_extract(exp.RegexpExtractAll), 459 "RLIKE": exp.RegexpLike.from_arg_list, 460 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 461 "TABLE": lambda args: exp.TableFromRows(this=seq_get(args, 0)), 462 "TIMEADD": _build_date_time_add(exp.TimeAdd), 463 "TIMEDIFF": _build_datediff, 464 "TIMESTAMPADD": _build_date_time_add(exp.DateAdd), 465 "TIMESTAMPDIFF": _build_datediff, 466 "TIMESTAMPFROMPARTS": build_timestamp_from_parts, 467 "TIMESTAMP_FROM_PARTS": build_timestamp_from_parts, 468 "TIMESTAMPNTZFROMPARTS": build_timestamp_from_parts, 469 "TIMESTAMP_NTZ_FROM_PARTS": build_timestamp_from_parts, 470 "TRY_PARSE_JSON": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True), 471 "TRY_TO_DATE": _build_datetime("TRY_TO_DATE", exp.DataType.Type.DATE, safe=True), 472 "TRY_TO_TIME": _build_datetime("TRY_TO_TIME", exp.DataType.Type.TIME, safe=True), 473 "TRY_TO_TIMESTAMP": _build_datetime( 474 "TRY_TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP, safe=True 475 ), 476 "TO_CHAR": build_timetostr_or_tochar, 477 "TO_DATE": _build_datetime("TO_DATE", exp.DataType.Type.DATE), 478 "TO_NUMBER": lambda args: exp.ToNumber( 479 this=seq_get(args, 0), 480 format=seq_get(args, 1), 481 precision=seq_get(args, 2), 482 scale=seq_get(args, 3), 483 ), 484 "TO_TIME": _build_datetime("TO_TIME", exp.DataType.Type.TIME), 485 "TO_TIMESTAMP": _build_datetime("TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP), 486 "TO_TIMESTAMP_LTZ": _build_datetime("TO_TIMESTAMP_LTZ", exp.DataType.Type.TIMESTAMPLTZ), 487 "TO_TIMESTAMP_NTZ": _build_datetime("TO_TIMESTAMP_NTZ", exp.DataType.Type.TIMESTAMP), 488 "TO_TIMESTAMP_TZ": _build_datetime("TO_TIMESTAMP_TZ", exp.DataType.Type.TIMESTAMPTZ), 489 "TO_VARCHAR": exp.ToChar.from_arg_list, 490 "ZEROIFNULL": _build_if_from_zeroifnull, 491 } 492 493 FUNCTION_PARSERS = { 494 **parser.Parser.FUNCTION_PARSERS, 495 "DATE_PART": lambda self: self._parse_date_part(), 496 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 497 "LISTAGG": lambda self: self._parse_string_agg(), 498 } 499 FUNCTION_PARSERS.pop("TRIM") 500 501 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 502 503 RANGE_PARSERS = { 504 **parser.Parser.RANGE_PARSERS, 505 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 506 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 507 } 508 509 ALTER_PARSERS = { 510 **parser.Parser.ALTER_PARSERS, 511 "UNSET": lambda self: self.expression( 512 exp.Set, 513 tag=self._match_text_seq("TAG"), 514 expressions=self._parse_csv(self._parse_id_var), 515 unset=True, 516 ), 517 } 518 519 STATEMENT_PARSERS = { 520 **parser.Parser.STATEMENT_PARSERS, 521 TokenType.GET: lambda self: self._parse_get(), 522 TokenType.PUT: lambda self: self._parse_put(), 523 TokenType.SHOW: lambda self: self._parse_show(), 524 } 525 526 PROPERTY_PARSERS = { 527 **parser.Parser.PROPERTY_PARSERS, 528 "CREDENTIALS": lambda self: self._parse_credentials_property(), 529 "FILE_FORMAT": lambda self: self._parse_file_format_property(), 530 "LOCATION": lambda self: self._parse_location_property(), 531 "TAG": lambda self: self._parse_tag(), 532 "USING": lambda self: self._match_text_seq("TEMPLATE") 533 and self.expression(exp.UsingTemplateProperty, this=self._parse_statement()), 534 } 535 536 TYPE_CONVERTERS = { 537 # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number 538 exp.DataType.Type.DECIMAL: build_default_decimal_type(precision=38, scale=0), 539 } 540 541 SHOW_PARSERS = { 542 "DATABASES": _show_parser("DATABASES"), 543 "TERSE DATABASES": _show_parser("DATABASES"), 544 "SCHEMAS": _show_parser("SCHEMAS"), 545 "TERSE SCHEMAS": _show_parser("SCHEMAS"), 546 "OBJECTS": _show_parser("OBJECTS"), 547 "TERSE OBJECTS": _show_parser("OBJECTS"), 548 "TABLES": _show_parser("TABLES"), 549 "TERSE TABLES": _show_parser("TABLES"), 550 "VIEWS": _show_parser("VIEWS"), 551 "TERSE VIEWS": _show_parser("VIEWS"), 552 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 553 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 554 "IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 555 "TERSE IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 556 "UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 557 "TERSE UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 558 "SEQUENCES": _show_parser("SEQUENCES"), 559 "TERSE SEQUENCES": _show_parser("SEQUENCES"), 560 "STAGES": _show_parser("STAGES"), 561 "COLUMNS": _show_parser("COLUMNS"), 562 "USERS": _show_parser("USERS"), 563 "TERSE USERS": _show_parser("USERS"), 564 "FILE FORMATS": _show_parser("FILE FORMATS"), 565 "FUNCTIONS": _show_parser("FUNCTIONS"), 566 "PROCEDURES": _show_parser("PROCEDURES"), 567 "WAREHOUSES": _show_parser("WAREHOUSES"), 568 } 569 570 CONSTRAINT_PARSERS = { 571 **parser.Parser.CONSTRAINT_PARSERS, 572 "WITH": lambda self: self._parse_with_constraint(), 573 "MASKING": lambda self: self._parse_with_constraint(), 574 "PROJECTION": lambda self: self._parse_with_constraint(), 575 "TAG": lambda self: self._parse_with_constraint(), 576 } 577 578 STAGED_FILE_SINGLE_TOKENS = { 579 TokenType.DOT, 580 TokenType.MOD, 581 TokenType.SLASH, 582 } 583 584 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 585 586 SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"} 587 588 NON_TABLE_CREATABLES = {"STORAGE INTEGRATION", "TAG", "WAREHOUSE", "STREAMLIT"} 589 590 LAMBDAS = { 591 **parser.Parser.LAMBDAS, 592 TokenType.ARROW: lambda self, expressions: self.expression( 593 exp.Lambda, 594 this=self._replace_lambda( 595 self._parse_assignment(), 596 expressions, 597 ), 598 expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions], 599 ), 600 } 601 602 def _parse_use(self) -> exp.Use: 603 if self._match_text_seq("SECONDARY", "ROLES"): 604 this = self._match_texts(("ALL", "NONE")) and exp.var(self._prev.text.upper()) 605 roles = None if this else self._parse_csv(lambda: self._parse_table(schema=False)) 606 return self.expression( 607 exp.Use, kind="SECONDARY ROLES", this=this, expressions=roles 608 ) 609 610 return super()._parse_use() 611 612 def _negate_range( 613 self, this: t.Optional[exp.Expression] = None 614 ) -> t.Optional[exp.Expression]: 615 if not this: 616 return this 617 618 query = this.args.get("query") 619 if isinstance(this, exp.In) and isinstance(query, exp.Query): 620 # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so 621 # we do this conversion here to avoid parsing it into `NOT value IN (subquery)` 622 # which can produce different results (most likely a SnowFlake bug). 623 # 624 # https://docs.snowflake.com/en/sql-reference/functions/in 625 # Context: https://github.com/tobymao/sqlglot/issues/3890 626 return self.expression( 627 exp.NEQ, this=this.this, expression=exp.All(this=query.unnest()) 628 ) 629 630 return self.expression(exp.Not, this=this) 631 632 def _parse_tag(self) -> exp.Tags: 633 return self.expression( 634 exp.Tags, 635 expressions=self._parse_wrapped_csv(self._parse_property), 636 ) 637 638 def _parse_with_constraint(self) -> t.Optional[exp.Expression]: 639 if self._prev.token_type != TokenType.WITH: 640 self._retreat(self._index - 1) 641 642 if self._match_text_seq("MASKING", "POLICY"): 643 policy = self._parse_column() 644 return self.expression( 645 exp.MaskingPolicyColumnConstraint, 646 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 647 expressions=self._match(TokenType.USING) 648 and self._parse_wrapped_csv(self._parse_id_var), 649 ) 650 if self._match_text_seq("PROJECTION", "POLICY"): 651 policy = self._parse_column() 652 return self.expression( 653 exp.ProjectionPolicyColumnConstraint, 654 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 655 ) 656 if self._match(TokenType.TAG): 657 return self._parse_tag() 658 659 return None 660 661 def _parse_with_property(self) -> t.Optional[exp.Expression] | t.List[exp.Expression]: 662 if self._match(TokenType.TAG): 663 return self._parse_tag() 664 665 return super()._parse_with_property() 666 667 def _parse_create(self) -> exp.Create | exp.Command: 668 expression = super()._parse_create() 669 if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES: 670 # Replace the Table node with the enclosed Identifier 671 expression.this.replace(expression.this.this) 672 673 return expression 674 675 # https://docs.snowflake.com/en/sql-reference/functions/date_part.html 676 # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 677 def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 678 this = self._parse_var() or self._parse_type() 679 680 if not this: 681 return None 682 683 self._match(TokenType.COMMA) 684 expression = self._parse_bitwise() 685 this = map_date_part(this) 686 name = this.name.upper() 687 688 if name.startswith("EPOCH"): 689 if name == "EPOCH_MILLISECOND": 690 scale = 10**3 691 elif name == "EPOCH_MICROSECOND": 692 scale = 10**6 693 elif name == "EPOCH_NANOSECOND": 694 scale = 10**9 695 else: 696 scale = None 697 698 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 699 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 700 701 if scale: 702 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 703 704 return to_unix 705 706 return self.expression(exp.Extract, this=this, expression=expression) 707 708 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 709 if is_map: 710 # Keys are strings in Snowflake's objects, see also: 711 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 712 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 713 return self._parse_slice(self._parse_string()) 714 715 return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True)) 716 717 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 718 lateral = super()._parse_lateral() 719 if not lateral: 720 return lateral 721 722 if isinstance(lateral.this, exp.Explode): 723 table_alias = lateral.args.get("alias") 724 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 725 if table_alias and not table_alias.args.get("columns"): 726 table_alias.set("columns", columns) 727 elif not table_alias: 728 exp.alias_(lateral, "_flattened", table=columns, copy=False) 729 730 return lateral 731 732 def _parse_table_parts( 733 self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False 734 ) -> exp.Table: 735 # https://docs.snowflake.com/en/user-guide/querying-stage 736 if self._match(TokenType.STRING, advance=False): 737 table = self._parse_string() 738 elif self._match_text_seq("@", advance=False): 739 table = self._parse_location_path() 740 else: 741 table = None 742 743 if table: 744 file_format = None 745 pattern = None 746 747 wrapped = self._match(TokenType.L_PAREN) 748 while self._curr and wrapped and not self._match(TokenType.R_PAREN): 749 if self._match_text_seq("FILE_FORMAT", "=>"): 750 file_format = self._parse_string() or super()._parse_table_parts( 751 is_db_reference=is_db_reference 752 ) 753 elif self._match_text_seq("PATTERN", "=>"): 754 pattern = self._parse_string() 755 else: 756 break 757 758 self._match(TokenType.COMMA) 759 760 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 761 else: 762 table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 763 764 return table 765 766 def _parse_table( 767 self, 768 schema: bool = False, 769 joins: bool = False, 770 alias_tokens: t.Optional[t.Collection[TokenType]] = None, 771 parse_bracket: bool = False, 772 is_db_reference: bool = False, 773 parse_partition: bool = False, 774 ) -> t.Optional[exp.Expression]: 775 table = super()._parse_table( 776 schema=schema, 777 joins=joins, 778 alias_tokens=alias_tokens, 779 parse_bracket=parse_bracket, 780 is_db_reference=is_db_reference, 781 parse_partition=parse_partition, 782 ) 783 if isinstance(table, exp.Table) and isinstance(table.this, exp.TableFromRows): 784 table_from_rows = table.this 785 for arg in exp.TableFromRows.arg_types: 786 if arg != "this": 787 table_from_rows.set(arg, table.args.get(arg)) 788 789 table = table_from_rows 790 791 return table 792 793 def _parse_id_var( 794 self, 795 any_token: bool = True, 796 tokens: t.Optional[t.Collection[TokenType]] = None, 797 ) -> t.Optional[exp.Expression]: 798 if self._match_text_seq("IDENTIFIER", "("): 799 identifier = ( 800 super()._parse_id_var(any_token=any_token, tokens=tokens) 801 or self._parse_string() 802 ) 803 self._match_r_paren() 804 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 805 806 return super()._parse_id_var(any_token=any_token, tokens=tokens) 807 808 def _parse_show_snowflake(self, this: str) -> exp.Show: 809 scope = None 810 scope_kind = None 811 812 # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS 813 # which is syntactically valid but has no effect on the output 814 terse = self._tokens[self._index - 2].text.upper() == "TERSE" 815 816 history = self._match_text_seq("HISTORY") 817 818 like = self._parse_string() if self._match(TokenType.LIKE) else None 819 820 if self._match(TokenType.IN): 821 if self._match_text_seq("ACCOUNT"): 822 scope_kind = "ACCOUNT" 823 elif self._match_text_seq("CLASS"): 824 scope_kind = "CLASS" 825 scope = self._parse_table_parts() 826 elif self._match_text_seq("APPLICATION"): 827 scope_kind = "APPLICATION" 828 if self._match_text_seq("PACKAGE"): 829 scope_kind += " PACKAGE" 830 scope = self._parse_table_parts() 831 elif self._match_set(self.DB_CREATABLES): 832 scope_kind = self._prev.text.upper() 833 if self._curr: 834 scope = self._parse_table_parts() 835 elif self._curr: 836 scope_kind = "SCHEMA" if this in self.SCHEMA_KINDS else "TABLE" 837 scope = self._parse_table_parts() 838 839 return self.expression( 840 exp.Show, 841 **{ 842 "terse": terse, 843 "this": this, 844 "history": history, 845 "like": like, 846 "scope": scope, 847 "scope_kind": scope_kind, 848 "starts_with": self._match_text_seq("STARTS", "WITH") and self._parse_string(), 849 "limit": self._parse_limit(), 850 "from": self._parse_string() if self._match(TokenType.FROM) else None, 851 "privileges": self._match_text_seq("WITH", "PRIVILEGES") 852 and self._parse_csv(lambda: self._parse_var(any_token=True, upper=True)), 853 }, 854 ) 855 856 def _parse_put(self) -> exp.Put | exp.Command: 857 if self._curr.token_type != TokenType.STRING: 858 return self._parse_as_command(self._prev) 859 860 return self.expression( 861 exp.Put, 862 this=self._parse_string(), 863 target=self._parse_location_path(), 864 properties=self._parse_properties(), 865 ) 866 867 def _parse_get(self) -> t.Optional[exp.Expression]: 868 start = self._prev 869 870 # If we detect GET( then we need to parse a function, not a statement 871 if self._match(TokenType.L_PAREN): 872 self._retreat(self._index - 2) 873 return self._parse_expression() 874 875 target = self._parse_location_path() 876 877 # Parse as command if unquoted file path 878 if self._curr.token_type == TokenType.URI_START: 879 return self._parse_as_command(start) 880 881 return self.expression( 882 exp.Get, 883 this=self._parse_string(), 884 target=target, 885 properties=self._parse_properties(), 886 ) 887 888 def _parse_location_property(self) -> exp.LocationProperty: 889 self._match(TokenType.EQ) 890 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 891 892 def _parse_file_location(self) -> t.Optional[exp.Expression]: 893 # Parse either a subquery or a staged file 894 return ( 895 self._parse_select(table=True, parse_subquery_alias=False) 896 if self._match(TokenType.L_PAREN, advance=False) 897 else self._parse_table_parts() 898 ) 899 900 def _parse_location_path(self) -> exp.Var: 901 start = self._curr 902 self._advance_any(ignore_reserved=True) 903 904 # We avoid consuming a comma token because external tables like @foo and @bar 905 # can be joined in a query with a comma separator, as well as closing paren 906 # in case of subqueries 907 while self._is_connected() and not self._match_set( 908 (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False 909 ): 910 self._advance_any(ignore_reserved=True) 911 912 return exp.var(self._find_sql(start, self._prev)) 913 914 def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: 915 this = super()._parse_lambda_arg() 916 917 if not this: 918 return this 919 920 typ = self._parse_types() 921 922 if typ: 923 return self.expression(exp.Cast, this=this, to=typ) 924 925 return this 926 927 def _parse_foreign_key(self) -> exp.ForeignKey: 928 # inlineFK, the REFERENCES columns are implied 929 if self._match(TokenType.REFERENCES, advance=False): 930 return self.expression(exp.ForeignKey) 931 932 # outoflineFK, explicitly names the columns 933 return super()._parse_foreign_key() 934 935 def _parse_file_format_property(self) -> exp.FileFormatProperty: 936 self._match(TokenType.EQ) 937 if self._match(TokenType.L_PAREN, advance=False): 938 expressions = self._parse_wrapped_options() 939 else: 940 expressions = [self._parse_format_name()] 941 942 return self.expression( 943 exp.FileFormatProperty, 944 expressions=expressions, 945 ) 946 947 def _parse_credentials_property(self) -> exp.CredentialsProperty: 948 return self.expression( 949 exp.CredentialsProperty, 950 expressions=self._parse_wrapped_options(), 951 ) 952 953 class Tokenizer(tokens.Tokenizer): 954 STRING_ESCAPES = ["\\", "'"] 955 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 956 RAW_STRINGS = ["$$"] 957 COMMENTS = ["--", "//", ("/*", "*/")] 958 NESTED_COMMENTS = False 959 960 KEYWORDS = { 961 **tokens.Tokenizer.KEYWORDS, 962 "FILE://": TokenType.URI_START, 963 "BYTEINT": TokenType.INT, 964 "EXCLUDE": TokenType.EXCEPT, 965 "FILE FORMAT": TokenType.FILE_FORMAT, 966 "GET": TokenType.GET, 967 "ILIKE ANY": TokenType.ILIKE_ANY, 968 "LIKE ANY": TokenType.LIKE_ANY, 969 "MATCH_CONDITION": TokenType.MATCH_CONDITION, 970 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 971 "MINUS": TokenType.EXCEPT, 972 "NCHAR VARYING": TokenType.VARCHAR, 973 "PUT": TokenType.PUT, 974 "REMOVE": TokenType.COMMAND, 975 "RM": TokenType.COMMAND, 976 "SAMPLE": TokenType.TABLE_SAMPLE, 977 "SQL_DOUBLE": TokenType.DOUBLE, 978 "SQL_VARCHAR": TokenType.VARCHAR, 979 "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION, 980 "TAG": TokenType.TAG, 981 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 982 "TOP": TokenType.TOP, 983 "WAREHOUSE": TokenType.WAREHOUSE, 984 "STAGE": TokenType.STAGE, 985 "STREAMLIT": TokenType.STREAMLIT, 986 } 987 KEYWORDS.pop("/*+") 988 989 SINGLE_TOKENS = { 990 **tokens.Tokenizer.SINGLE_TOKENS, 991 "$": TokenType.PARAMETER, 992 } 993 994 VAR_SINGLE_TOKENS = {"$"} 995 996 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 997 998 class Generator(generator.Generator): 999 PARAMETER_TOKEN = "$" 1000 MATCHED_BY_SOURCE = False 1001 SINGLE_STRING_INTERVAL = True 1002 JOIN_HINTS = False 1003 TABLE_HINTS = False 1004 QUERY_HINTS = False 1005 AGGREGATE_FILTER_SUPPORTED = False 1006 SUPPORTS_TABLE_COPY = False 1007 COLLATE_IS_FUNC = True 1008 LIMIT_ONLY_LITERALS = True 1009 JSON_KEY_VALUE_PAIR_SEP = "," 1010 INSERT_OVERWRITE = " OVERWRITE INTO" 1011 STRUCT_DELIMITER = ("(", ")") 1012 COPY_PARAMS_ARE_WRAPPED = False 1013 COPY_PARAMS_EQ_REQUIRED = True 1014 STAR_EXCEPT = "EXCLUDE" 1015 SUPPORTS_EXPLODING_PROJECTIONS = False 1016 ARRAY_CONCAT_IS_VAR_LEN = False 1017 SUPPORTS_CONVERT_TIMEZONE = True 1018 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 1019 SUPPORTS_MEDIAN = True 1020 ARRAY_SIZE_NAME = "ARRAY_SIZE" 1021 1022 TRANSFORMS = { 1023 **generator.Generator.TRANSFORMS, 1024 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 1025 exp.ArgMax: rename_func("MAX_BY"), 1026 exp.ArgMin: rename_func("MIN_BY"), 1027 exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"), 1028 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 1029 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 1030 exp.AtTimeZone: lambda self, e: self.func( 1031 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 1032 ), 1033 exp.BitwiseOr: rename_func("BITOR"), 1034 exp.BitwiseXor: rename_func("BITXOR"), 1035 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 1036 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 1037 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 1038 exp.DateAdd: date_delta_sql("DATEADD"), 1039 exp.DateDiff: date_delta_sql("DATEDIFF"), 1040 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 1041 exp.DatetimeDiff: timestampdiff_sql, 1042 exp.DateStrToDate: datestrtodate_sql, 1043 exp.DayOfMonth: rename_func("DAYOFMONTH"), 1044 exp.DayOfWeek: rename_func("DAYOFWEEK"), 1045 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 1046 exp.DayOfYear: rename_func("DAYOFYEAR"), 1047 exp.Explode: rename_func("FLATTEN"), 1048 exp.Extract: lambda self, e: self.func( 1049 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 1050 ), 1051 exp.FileFormatProperty: lambda self, 1052 e: f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})", 1053 exp.FromTimeZone: lambda self, e: self.func( 1054 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 1055 ), 1056 exp.GenerateSeries: lambda self, e: self.func( 1057 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 1058 ), 1059 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 1060 exp.If: if_sql(name="IFF", false_value="NULL"), 1061 exp.JSONExtractArray: _json_extract_value_array_sql, 1062 exp.JSONExtractScalar: lambda self, e: self.func( 1063 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 1064 ), 1065 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 1066 exp.JSONPathRoot: lambda *_: "", 1067 exp.JSONValueArray: _json_extract_value_array_sql, 1068 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 1069 rename_func("EDITDISTANCE") 1070 ), 1071 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 1072 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 1073 exp.LogicalOr: rename_func("BOOLOR_AGG"), 1074 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 1075 exp.MakeInterval: no_make_interval_sql, 1076 exp.Max: max_or_greatest, 1077 exp.Min: min_or_least, 1078 exp.ParseJSON: lambda self, e: self.func( 1079 "TRY_PARSE_JSON" if e.args.get("safe") else "PARSE_JSON", e.this 1080 ), 1081 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 1082 exp.PercentileCont: transforms.preprocess( 1083 [transforms.add_within_group_for_percentiles] 1084 ), 1085 exp.PercentileDisc: transforms.preprocess( 1086 [transforms.add_within_group_for_percentiles] 1087 ), 1088 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 1089 exp.RegexpExtract: _regexpextract_sql, 1090 exp.RegexpExtractAll: _regexpextract_sql, 1091 exp.RegexpILike: _regexpilike_sql, 1092 exp.Rand: rename_func("RANDOM"), 1093 exp.Select: transforms.preprocess( 1094 [ 1095 transforms.eliminate_window_clause, 1096 transforms.eliminate_distinct_on, 1097 transforms.explode_projection_to_unnest(), 1098 transforms.eliminate_semi_and_anti_joins, 1099 _transform_generate_date_array, 1100 ] 1101 ), 1102 exp.SHA: rename_func("SHA1"), 1103 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 1104 exp.StartsWith: rename_func("STARTSWITH"), 1105 exp.EndsWith: rename_func("ENDSWITH"), 1106 exp.StrPosition: lambda self, e: strposition_sql( 1107 self, e, func_name="CHARINDEX", supports_position=True 1108 ), 1109 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 1110 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 1111 exp.Stuff: rename_func("INSERT"), 1112 exp.StPoint: rename_func("ST_MAKEPOINT"), 1113 exp.TimeAdd: date_delta_sql("TIMEADD"), 1114 exp.Timestamp: no_timestamp_sql, 1115 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 1116 exp.TimestampDiff: lambda self, e: self.func( 1117 "TIMESTAMPDIFF", e.unit, e.expression, e.this 1118 ), 1119 exp.TimestampTrunc: timestamptrunc_sql(), 1120 exp.TimeStrToTime: timestrtotime_sql, 1121 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 1122 exp.ToArray: rename_func("TO_ARRAY"), 1123 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 1124 exp.ToDouble: rename_func("TO_DOUBLE"), 1125 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 1126 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 1127 exp.TsOrDsToDate: lambda self, e: self.func( 1128 "TRY_TO_DATE" if e.args.get("safe") else "TO_DATE", e.this, self.format_time(e) 1129 ), 1130 exp.TsOrDsToTime: lambda self, e: self.func( 1131 "TRY_TO_TIME" if e.args.get("safe") else "TO_TIME", e.this, self.format_time(e) 1132 ), 1133 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 1134 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 1135 exp.Uuid: rename_func("UUID_STRING"), 1136 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 1137 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 1138 exp.Xor: rename_func("BOOLXOR"), 1139 } 1140 1141 SUPPORTED_JSON_PATH_PARTS = { 1142 exp.JSONPathKey, 1143 exp.JSONPathRoot, 1144 exp.JSONPathSubscript, 1145 } 1146 1147 TYPE_MAPPING = { 1148 **generator.Generator.TYPE_MAPPING, 1149 exp.DataType.Type.NESTED: "OBJECT", 1150 exp.DataType.Type.STRUCT: "OBJECT", 1151 exp.DataType.Type.BIGDECIMAL: "DOUBLE", 1152 } 1153 1154 TOKEN_MAPPING = { 1155 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 1156 } 1157 1158 PROPERTIES_LOCATION = { 1159 **generator.Generator.PROPERTIES_LOCATION, 1160 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 1161 exp.LocationProperty: exp.Properties.Location.POST_WITH, 1162 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 1163 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 1164 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 1165 } 1166 1167 UNSUPPORTED_VALUES_EXPRESSIONS = { 1168 exp.Map, 1169 exp.StarMap, 1170 exp.Struct, 1171 exp.VarMap, 1172 } 1173 1174 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 1175 1176 def with_properties(self, properties: exp.Properties) -> str: 1177 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 1178 1179 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 1180 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 1181 values_as_table = False 1182 1183 return super().values_sql(expression, values_as_table=values_as_table) 1184 1185 def datatype_sql(self, expression: exp.DataType) -> str: 1186 expressions = expression.expressions 1187 if ( 1188 expressions 1189 and expression.is_type(*exp.DataType.STRUCT_TYPES) 1190 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 1191 ): 1192 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 1193 return "OBJECT" 1194 1195 return super().datatype_sql(expression) 1196 1197 def tonumber_sql(self, expression: exp.ToNumber) -> str: 1198 return self.func( 1199 "TO_NUMBER", 1200 expression.this, 1201 expression.args.get("format"), 1202 expression.args.get("precision"), 1203 expression.args.get("scale"), 1204 ) 1205 1206 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 1207 milli = expression.args.get("milli") 1208 if milli is not None: 1209 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 1210 expression.set("nano", milli_to_nano) 1211 1212 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 1213 1214 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1215 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 1216 return self.func("TO_GEOGRAPHY", expression.this) 1217 if expression.is_type(exp.DataType.Type.GEOMETRY): 1218 return self.func("TO_GEOMETRY", expression.this) 1219 1220 return super().cast_sql(expression, safe_prefix=safe_prefix) 1221 1222 def trycast_sql(self, expression: exp.TryCast) -> str: 1223 value = expression.this 1224 1225 if value.type is None: 1226 from sqlglot.optimizer.annotate_types import annotate_types 1227 1228 value = annotate_types(value, dialect=self.dialect) 1229 1230 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 1231 return super().trycast_sql(expression) 1232 1233 # TRY_CAST only works for string values in Snowflake 1234 return self.cast_sql(expression) 1235 1236 def log_sql(self, expression: exp.Log) -> str: 1237 if not expression.expression: 1238 return self.func("LN", expression.this) 1239 1240 return super().log_sql(expression) 1241 1242 def unnest_sql(self, expression: exp.Unnest) -> str: 1243 unnest_alias = expression.args.get("alias") 1244 offset = expression.args.get("offset") 1245 1246 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 1247 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 1248 1249 columns = [ 1250 exp.to_identifier("seq"), 1251 exp.to_identifier("key"), 1252 exp.to_identifier("path"), 1253 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 1254 value, 1255 exp.to_identifier("this"), 1256 ] 1257 1258 if unnest_alias: 1259 unnest_alias.set("columns", columns) 1260 else: 1261 unnest_alias = exp.TableAlias(this="_u", columns=columns) 1262 1263 table_input = self.sql(expression.expressions[0]) 1264 if not table_input.startswith("INPUT =>"): 1265 table_input = f"INPUT => {table_input}" 1266 1267 explode = f"TABLE(FLATTEN({table_input}))" 1268 alias = self.sql(unnest_alias) 1269 alias = f" AS {alias}" if alias else "" 1270 value = "" if isinstance(expression.parent, (exp.From, exp.Join)) else f"{value} FROM " 1271 1272 return f"{value}{explode}{alias}" 1273 1274 def show_sql(self, expression: exp.Show) -> str: 1275 terse = "TERSE " if expression.args.get("terse") else "" 1276 history = " HISTORY" if expression.args.get("history") else "" 1277 like = self.sql(expression, "like") 1278 like = f" LIKE {like}" if like else "" 1279 1280 scope = self.sql(expression, "scope") 1281 scope = f" {scope}" if scope else "" 1282 1283 scope_kind = self.sql(expression, "scope_kind") 1284 if scope_kind: 1285 scope_kind = f" IN {scope_kind}" 1286 1287 starts_with = self.sql(expression, "starts_with") 1288 if starts_with: 1289 starts_with = f" STARTS WITH {starts_with}" 1290 1291 limit = self.sql(expression, "limit") 1292 1293 from_ = self.sql(expression, "from") 1294 if from_: 1295 from_ = f" FROM {from_}" 1296 1297 privileges = self.expressions(expression, key="privileges", flat=True) 1298 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 1299 1300 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 1301 1302 def describe_sql(self, expression: exp.Describe) -> str: 1303 # Default to table if kind is unknown 1304 kind_value = expression.args.get("kind") or "TABLE" 1305 kind = f" {kind_value}" if kind_value else "" 1306 this = f" {self.sql(expression, 'this')}" 1307 expressions = self.expressions(expression, flat=True) 1308 expressions = f" {expressions}" if expressions else "" 1309 return f"DESCRIBE{kind}{this}{expressions}" 1310 1311 def generatedasidentitycolumnconstraint_sql( 1312 self, expression: exp.GeneratedAsIdentityColumnConstraint 1313 ) -> str: 1314 start = expression.args.get("start") 1315 start = f" START {start}" if start else "" 1316 increment = expression.args.get("increment") 1317 increment = f" INCREMENT {increment}" if increment else "" 1318 return f"AUTOINCREMENT{start}{increment}" 1319 1320 def cluster_sql(self, expression: exp.Cluster) -> str: 1321 return f"CLUSTER BY ({self.expressions(expression, flat=True)})" 1322 1323 def struct_sql(self, expression: exp.Struct) -> str: 1324 keys = [] 1325 values = [] 1326 1327 for i, e in enumerate(expression.expressions): 1328 if isinstance(e, exp.PropertyEQ): 1329 keys.append( 1330 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1331 ) 1332 values.append(e.expression) 1333 else: 1334 keys.append(exp.Literal.string(f"_{i}")) 1335 values.append(e) 1336 1337 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 1338 1339 @unsupported_args("weight", "accuracy") 1340 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 1341 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 1342 1343 def alterset_sql(self, expression: exp.AlterSet) -> str: 1344 exprs = self.expressions(expression, flat=True) 1345 exprs = f" {exprs}" if exprs else "" 1346 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1347 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1348 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1349 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1350 tag = self.expressions(expression, key="tag", flat=True) 1351 tag = f" TAG {tag}" if tag else "" 1352 1353 return f"SET{exprs}{file_format}{copy_options}{tag}" 1354 1355 def strtotime_sql(self, expression: exp.StrToTime): 1356 safe_prefix = "TRY_" if expression.args.get("safe") else "" 1357 return self.func( 1358 f"{safe_prefix}TO_TIMESTAMP", expression.this, self.format_time(expression) 1359 ) 1360 1361 def timestampsub_sql(self, expression: exp.TimestampSub): 1362 return self.sql( 1363 exp.TimestampAdd( 1364 this=expression.this, 1365 expression=expression.expression * -1, 1366 unit=expression.unit, 1367 ) 1368 ) 1369 1370 def jsonextract_sql(self, expression: exp.JSONExtract): 1371 this = expression.this 1372 1373 # JSON strings are valid coming from other dialects such as BQ 1374 return self.func( 1375 "GET_PATH", 1376 exp.ParseJSON(this=this) if this.is_string else this, 1377 expression.expression, 1378 ) 1379 1380 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 1381 this = expression.this 1382 if not isinstance(this, exp.TsOrDsToTimestamp): 1383 this = exp.cast(this, exp.DataType.Type.TIMESTAMP) 1384 1385 return self.func("TO_CHAR", this, self.format_time(expression)) 1386 1387 def datesub_sql(self, expression: exp.DateSub) -> str: 1388 value = expression.expression 1389 if value: 1390 value.replace(value * (-1)) 1391 else: 1392 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1393 1394 return date_delta_sql("DATEADD")(self, expression) 1395 1396 def select_sql(self, expression: exp.Select) -> str: 1397 limit = expression.args.get("limit") 1398 offset = expression.args.get("offset") 1399 if offset and not limit: 1400 expression.limit(exp.Null(), copy=False) 1401 return super().select_sql(expression) 1402 1403 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1404 is_materialized = expression.find(exp.MaterializedProperty) 1405 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1406 1407 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1408 # For materialized views, COPY GRANTS is located *before* the columns list 1409 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1410 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1411 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1412 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1413 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1414 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1415 1416 this_name = self.sql(expression.this, "this") 1417 copy_grants = self.sql(copy_grants_property) 1418 this_schema = self.schema_columns_sql(expression.this) 1419 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1420 1421 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1422 1423 return super().createable_sql(expression, locations) 1424 1425 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1426 this = expression.this 1427 1428 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1429 # and add it later as part of the WITHIN GROUP clause 1430 order = this if isinstance(this, exp.Order) else None 1431 if order: 1432 expression.set("this", order.this.pop()) 1433 1434 expr_sql = super().arrayagg_sql(expression) 1435 1436 if order: 1437 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1438 1439 return expr_sql 1440 1441 def array_sql(self, expression: exp.Array) -> str: 1442 expressions = expression.expressions 1443 1444 first_expr = seq_get(expressions, 0) 1445 if isinstance(first_expr, exp.Select): 1446 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1447 if first_expr.text("kind").upper() == "STRUCT": 1448 object_construct_args = [] 1449 for expr in first_expr.expressions: 1450 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1451 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1452 name = expr.this if isinstance(expr, exp.Alias) else expr 1453 1454 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1455 1456 array_agg = exp.ArrayAgg( 1457 this=_build_object_construct(args=object_construct_args) 1458 ) 1459 1460 first_expr.set("kind", None) 1461 first_expr.set("expressions", [array_agg]) 1462 1463 return self.sql(first_expr.subquery()) 1464 1465 return inline_array_sql(self, expression)
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.
381 def quote_identifier(self, expression: E, identify: bool = True) -> E: 382 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 383 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 384 if ( 385 isinstance(expression, exp.Identifier) 386 and isinstance(expression.parent, exp.Table) 387 and expression.name.lower() == "dual" 388 ): 389 return expression # type: ignore 390 391 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 (
).
393 class JSONPathTokenizer(jsonpath.JSONPathTokenizer): 394 SINGLE_TOKENS = jsonpath.JSONPathTokenizer.SINGLE_TOKENS.copy() 395 SINGLE_TOKENS.pop("$")
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- BIT_STRINGS
- BYTE_STRINGS
- HEX_STRINGS
- RAW_STRINGS
- HEREDOC_STRINGS
- UNICODE_STRINGS
- IDENTIFIERS
- QUOTES
- VAR_SINGLE_TOKENS
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- NESTED_COMMENTS
- HINT_START
- TOKENS_PRECEDING_HINT
- WHITE_SPACE
- COMMANDS
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- COMMENTS
- dialect
- use_rs_tokenizer
- reset
- tokenize
- tokenize_rs
- size
- sql
- tokens
397 class Parser(parser.Parser): 398 IDENTIFY_PIVOT_STRINGS = True 399 DEFAULT_SAMPLING_METHOD = "BERNOULLI" 400 COLON_IS_VARIANT_EXTRACT = True 401 402 ID_VAR_TOKENS = { 403 *parser.Parser.ID_VAR_TOKENS, 404 TokenType.MATCH_CONDITION, 405 } 406 407 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 408 TABLE_ALIAS_TOKENS.discard(TokenType.MATCH_CONDITION) 409 410 COLON_PLACEHOLDER_TOKENS = ID_VAR_TOKENS | {TokenType.NUMBER} 411 412 FUNCTIONS = { 413 **parser.Parser.FUNCTIONS, 414 "APPROX_PERCENTILE": exp.ApproxQuantile.from_arg_list, 415 "ARRAY_CONSTRUCT": lambda args: exp.Array(expressions=args), 416 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 417 this=seq_get(args, 1), expression=seq_get(args, 0) 418 ), 419 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 420 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 421 start=seq_get(args, 0), 422 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 423 step=seq_get(args, 2), 424 ), 425 "BITXOR": _build_bitwise(exp.BitwiseXor, "BITXOR"), 426 "BIT_XOR": _build_bitwise(exp.BitwiseXor, "BITXOR"), 427 "BITOR": _build_bitwise(exp.BitwiseOr, "BITOR"), 428 "BIT_OR": _build_bitwise(exp.BitwiseOr, "BITOR"), 429 "BITSHIFTLEFT": _build_bitwise(exp.BitwiseLeftShift, "BITSHIFTLEFT"), 430 "BIT_SHIFTLEFT": _build_bitwise(exp.BitwiseLeftShift, "BIT_SHIFTLEFT"), 431 "BITSHIFTRIGHT": _build_bitwise(exp.BitwiseRightShift, "BITSHIFTRIGHT"), 432 "BIT_SHIFTRIGHT": _build_bitwise(exp.BitwiseRightShift, "BIT_SHIFTRIGHT"), 433 "BOOLXOR": _build_bitwise(exp.Xor, "BOOLXOR"), 434 "DATE": _build_datetime("DATE", exp.DataType.Type.DATE), 435 "DATE_TRUNC": _date_trunc_to_time, 436 "DATEADD": _build_date_time_add(exp.DateAdd), 437 "DATEDIFF": _build_datediff, 438 "DIV0": _build_if_from_div0, 439 "EDITDISTANCE": lambda args: exp.Levenshtein( 440 this=seq_get(args, 0), expression=seq_get(args, 1), max_dist=seq_get(args, 2) 441 ), 442 "FLATTEN": exp.Explode.from_arg_list, 443 "GET_PATH": lambda args, dialect: exp.JSONExtract( 444 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 445 ), 446 "HEX_DECODE_BINARY": exp.Unhex.from_arg_list, 447 "IFF": exp.If.from_arg_list, 448 "LAST_DAY": lambda args: exp.LastDay( 449 this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1)) 450 ), 451 "LEN": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 452 "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), 453 "NULLIFZERO": _build_if_from_nullifzero, 454 "OBJECT_CONSTRUCT": _build_object_construct, 455 "REGEXP_EXTRACT_ALL": _build_regexp_extract(exp.RegexpExtractAll), 456 "REGEXP_REPLACE": _build_regexp_replace, 457 "REGEXP_SUBSTR": _build_regexp_extract(exp.RegexpExtract), 458 "REGEXP_SUBSTR_ALL": _build_regexp_extract(exp.RegexpExtractAll), 459 "RLIKE": exp.RegexpLike.from_arg_list, 460 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 461 "TABLE": lambda args: exp.TableFromRows(this=seq_get(args, 0)), 462 "TIMEADD": _build_date_time_add(exp.TimeAdd), 463 "TIMEDIFF": _build_datediff, 464 "TIMESTAMPADD": _build_date_time_add(exp.DateAdd), 465 "TIMESTAMPDIFF": _build_datediff, 466 "TIMESTAMPFROMPARTS": build_timestamp_from_parts, 467 "TIMESTAMP_FROM_PARTS": build_timestamp_from_parts, 468 "TIMESTAMPNTZFROMPARTS": build_timestamp_from_parts, 469 "TIMESTAMP_NTZ_FROM_PARTS": build_timestamp_from_parts, 470 "TRY_PARSE_JSON": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True), 471 "TRY_TO_DATE": _build_datetime("TRY_TO_DATE", exp.DataType.Type.DATE, safe=True), 472 "TRY_TO_TIME": _build_datetime("TRY_TO_TIME", exp.DataType.Type.TIME, safe=True), 473 "TRY_TO_TIMESTAMP": _build_datetime( 474 "TRY_TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP, safe=True 475 ), 476 "TO_CHAR": build_timetostr_or_tochar, 477 "TO_DATE": _build_datetime("TO_DATE", exp.DataType.Type.DATE), 478 "TO_NUMBER": lambda args: exp.ToNumber( 479 this=seq_get(args, 0), 480 format=seq_get(args, 1), 481 precision=seq_get(args, 2), 482 scale=seq_get(args, 3), 483 ), 484 "TO_TIME": _build_datetime("TO_TIME", exp.DataType.Type.TIME), 485 "TO_TIMESTAMP": _build_datetime("TO_TIMESTAMP", exp.DataType.Type.TIMESTAMP), 486 "TO_TIMESTAMP_LTZ": _build_datetime("TO_TIMESTAMP_LTZ", exp.DataType.Type.TIMESTAMPLTZ), 487 "TO_TIMESTAMP_NTZ": _build_datetime("TO_TIMESTAMP_NTZ", exp.DataType.Type.TIMESTAMP), 488 "TO_TIMESTAMP_TZ": _build_datetime("TO_TIMESTAMP_TZ", exp.DataType.Type.TIMESTAMPTZ), 489 "TO_VARCHAR": exp.ToChar.from_arg_list, 490 "ZEROIFNULL": _build_if_from_zeroifnull, 491 } 492 493 FUNCTION_PARSERS = { 494 **parser.Parser.FUNCTION_PARSERS, 495 "DATE_PART": lambda self: self._parse_date_part(), 496 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 497 "LISTAGG": lambda self: self._parse_string_agg(), 498 } 499 FUNCTION_PARSERS.pop("TRIM") 500 501 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 502 503 RANGE_PARSERS = { 504 **parser.Parser.RANGE_PARSERS, 505 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 506 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 507 } 508 509 ALTER_PARSERS = { 510 **parser.Parser.ALTER_PARSERS, 511 "UNSET": lambda self: self.expression( 512 exp.Set, 513 tag=self._match_text_seq("TAG"), 514 expressions=self._parse_csv(self._parse_id_var), 515 unset=True, 516 ), 517 } 518 519 STATEMENT_PARSERS = { 520 **parser.Parser.STATEMENT_PARSERS, 521 TokenType.GET: lambda self: self._parse_get(), 522 TokenType.PUT: lambda self: self._parse_put(), 523 TokenType.SHOW: lambda self: self._parse_show(), 524 } 525 526 PROPERTY_PARSERS = { 527 **parser.Parser.PROPERTY_PARSERS, 528 "CREDENTIALS": lambda self: self._parse_credentials_property(), 529 "FILE_FORMAT": lambda self: self._parse_file_format_property(), 530 "LOCATION": lambda self: self._parse_location_property(), 531 "TAG": lambda self: self._parse_tag(), 532 "USING": lambda self: self._match_text_seq("TEMPLATE") 533 and self.expression(exp.UsingTemplateProperty, this=self._parse_statement()), 534 } 535 536 TYPE_CONVERTERS = { 537 # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number 538 exp.DataType.Type.DECIMAL: build_default_decimal_type(precision=38, scale=0), 539 } 540 541 SHOW_PARSERS = { 542 "DATABASES": _show_parser("DATABASES"), 543 "TERSE DATABASES": _show_parser("DATABASES"), 544 "SCHEMAS": _show_parser("SCHEMAS"), 545 "TERSE SCHEMAS": _show_parser("SCHEMAS"), 546 "OBJECTS": _show_parser("OBJECTS"), 547 "TERSE OBJECTS": _show_parser("OBJECTS"), 548 "TABLES": _show_parser("TABLES"), 549 "TERSE TABLES": _show_parser("TABLES"), 550 "VIEWS": _show_parser("VIEWS"), 551 "TERSE VIEWS": _show_parser("VIEWS"), 552 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 553 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 554 "IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 555 "TERSE IMPORTED KEYS": _show_parser("IMPORTED KEYS"), 556 "UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 557 "TERSE UNIQUE KEYS": _show_parser("UNIQUE KEYS"), 558 "SEQUENCES": _show_parser("SEQUENCES"), 559 "TERSE SEQUENCES": _show_parser("SEQUENCES"), 560 "STAGES": _show_parser("STAGES"), 561 "COLUMNS": _show_parser("COLUMNS"), 562 "USERS": _show_parser("USERS"), 563 "TERSE USERS": _show_parser("USERS"), 564 "FILE FORMATS": _show_parser("FILE FORMATS"), 565 "FUNCTIONS": _show_parser("FUNCTIONS"), 566 "PROCEDURES": _show_parser("PROCEDURES"), 567 "WAREHOUSES": _show_parser("WAREHOUSES"), 568 } 569 570 CONSTRAINT_PARSERS = { 571 **parser.Parser.CONSTRAINT_PARSERS, 572 "WITH": lambda self: self._parse_with_constraint(), 573 "MASKING": lambda self: self._parse_with_constraint(), 574 "PROJECTION": lambda self: self._parse_with_constraint(), 575 "TAG": lambda self: self._parse_with_constraint(), 576 } 577 578 STAGED_FILE_SINGLE_TOKENS = { 579 TokenType.DOT, 580 TokenType.MOD, 581 TokenType.SLASH, 582 } 583 584 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 585 586 SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"} 587 588 NON_TABLE_CREATABLES = {"STORAGE INTEGRATION", "TAG", "WAREHOUSE", "STREAMLIT"} 589 590 LAMBDAS = { 591 **parser.Parser.LAMBDAS, 592 TokenType.ARROW: lambda self, expressions: self.expression( 593 exp.Lambda, 594 this=self._replace_lambda( 595 self._parse_assignment(), 596 expressions, 597 ), 598 expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions], 599 ), 600 } 601 602 def _parse_use(self) -> exp.Use: 603 if self._match_text_seq("SECONDARY", "ROLES"): 604 this = self._match_texts(("ALL", "NONE")) and exp.var(self._prev.text.upper()) 605 roles = None if this else self._parse_csv(lambda: self._parse_table(schema=False)) 606 return self.expression( 607 exp.Use, kind="SECONDARY ROLES", this=this, expressions=roles 608 ) 609 610 return super()._parse_use() 611 612 def _negate_range( 613 self, this: t.Optional[exp.Expression] = None 614 ) -> t.Optional[exp.Expression]: 615 if not this: 616 return this 617 618 query = this.args.get("query") 619 if isinstance(this, exp.In) and isinstance(query, exp.Query): 620 # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so 621 # we do this conversion here to avoid parsing it into `NOT value IN (subquery)` 622 # which can produce different results (most likely a SnowFlake bug). 623 # 624 # https://docs.snowflake.com/en/sql-reference/functions/in 625 # Context: https://github.com/tobymao/sqlglot/issues/3890 626 return self.expression( 627 exp.NEQ, this=this.this, expression=exp.All(this=query.unnest()) 628 ) 629 630 return self.expression(exp.Not, this=this) 631 632 def _parse_tag(self) -> exp.Tags: 633 return self.expression( 634 exp.Tags, 635 expressions=self._parse_wrapped_csv(self._parse_property), 636 ) 637 638 def _parse_with_constraint(self) -> t.Optional[exp.Expression]: 639 if self._prev.token_type != TokenType.WITH: 640 self._retreat(self._index - 1) 641 642 if self._match_text_seq("MASKING", "POLICY"): 643 policy = self._parse_column() 644 return self.expression( 645 exp.MaskingPolicyColumnConstraint, 646 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 647 expressions=self._match(TokenType.USING) 648 and self._parse_wrapped_csv(self._parse_id_var), 649 ) 650 if self._match_text_seq("PROJECTION", "POLICY"): 651 policy = self._parse_column() 652 return self.expression( 653 exp.ProjectionPolicyColumnConstraint, 654 this=policy.to_dot() if isinstance(policy, exp.Column) else policy, 655 ) 656 if self._match(TokenType.TAG): 657 return self._parse_tag() 658 659 return None 660 661 def _parse_with_property(self) -> t.Optional[exp.Expression] | t.List[exp.Expression]: 662 if self._match(TokenType.TAG): 663 return self._parse_tag() 664 665 return super()._parse_with_property() 666 667 def _parse_create(self) -> exp.Create | exp.Command: 668 expression = super()._parse_create() 669 if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES: 670 # Replace the Table node with the enclosed Identifier 671 expression.this.replace(expression.this.this) 672 673 return expression 674 675 # https://docs.snowflake.com/en/sql-reference/functions/date_part.html 676 # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 677 def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 678 this = self._parse_var() or self._parse_type() 679 680 if not this: 681 return None 682 683 self._match(TokenType.COMMA) 684 expression = self._parse_bitwise() 685 this = map_date_part(this) 686 name = this.name.upper() 687 688 if name.startswith("EPOCH"): 689 if name == "EPOCH_MILLISECOND": 690 scale = 10**3 691 elif name == "EPOCH_MICROSECOND": 692 scale = 10**6 693 elif name == "EPOCH_NANOSECOND": 694 scale = 10**9 695 else: 696 scale = None 697 698 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 699 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 700 701 if scale: 702 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 703 704 return to_unix 705 706 return self.expression(exp.Extract, this=this, expression=expression) 707 708 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 709 if is_map: 710 # Keys are strings in Snowflake's objects, see also: 711 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 712 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 713 return self._parse_slice(self._parse_string()) 714 715 return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True)) 716 717 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 718 lateral = super()._parse_lateral() 719 if not lateral: 720 return lateral 721 722 if isinstance(lateral.this, exp.Explode): 723 table_alias = lateral.args.get("alias") 724 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 725 if table_alias and not table_alias.args.get("columns"): 726 table_alias.set("columns", columns) 727 elif not table_alias: 728 exp.alias_(lateral, "_flattened", table=columns, copy=False) 729 730 return lateral 731 732 def _parse_table_parts( 733 self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False 734 ) -> exp.Table: 735 # https://docs.snowflake.com/en/user-guide/querying-stage 736 if self._match(TokenType.STRING, advance=False): 737 table = self._parse_string() 738 elif self._match_text_seq("@", advance=False): 739 table = self._parse_location_path() 740 else: 741 table = None 742 743 if table: 744 file_format = None 745 pattern = None 746 747 wrapped = self._match(TokenType.L_PAREN) 748 while self._curr and wrapped and not self._match(TokenType.R_PAREN): 749 if self._match_text_seq("FILE_FORMAT", "=>"): 750 file_format = self._parse_string() or super()._parse_table_parts( 751 is_db_reference=is_db_reference 752 ) 753 elif self._match_text_seq("PATTERN", "=>"): 754 pattern = self._parse_string() 755 else: 756 break 757 758 self._match(TokenType.COMMA) 759 760 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 761 else: 762 table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 763 764 return table 765 766 def _parse_table( 767 self, 768 schema: bool = False, 769 joins: bool = False, 770 alias_tokens: t.Optional[t.Collection[TokenType]] = None, 771 parse_bracket: bool = False, 772 is_db_reference: bool = False, 773 parse_partition: bool = False, 774 ) -> t.Optional[exp.Expression]: 775 table = super()._parse_table( 776 schema=schema, 777 joins=joins, 778 alias_tokens=alias_tokens, 779 parse_bracket=parse_bracket, 780 is_db_reference=is_db_reference, 781 parse_partition=parse_partition, 782 ) 783 if isinstance(table, exp.Table) and isinstance(table.this, exp.TableFromRows): 784 table_from_rows = table.this 785 for arg in exp.TableFromRows.arg_types: 786 if arg != "this": 787 table_from_rows.set(arg, table.args.get(arg)) 788 789 table = table_from_rows 790 791 return table 792 793 def _parse_id_var( 794 self, 795 any_token: bool = True, 796 tokens: t.Optional[t.Collection[TokenType]] = None, 797 ) -> t.Optional[exp.Expression]: 798 if self._match_text_seq("IDENTIFIER", "("): 799 identifier = ( 800 super()._parse_id_var(any_token=any_token, tokens=tokens) 801 or self._parse_string() 802 ) 803 self._match_r_paren() 804 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 805 806 return super()._parse_id_var(any_token=any_token, tokens=tokens) 807 808 def _parse_show_snowflake(self, this: str) -> exp.Show: 809 scope = None 810 scope_kind = None 811 812 # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS 813 # which is syntactically valid but has no effect on the output 814 terse = self._tokens[self._index - 2].text.upper() == "TERSE" 815 816 history = self._match_text_seq("HISTORY") 817 818 like = self._parse_string() if self._match(TokenType.LIKE) else None 819 820 if self._match(TokenType.IN): 821 if self._match_text_seq("ACCOUNT"): 822 scope_kind = "ACCOUNT" 823 elif self._match_text_seq("CLASS"): 824 scope_kind = "CLASS" 825 scope = self._parse_table_parts() 826 elif self._match_text_seq("APPLICATION"): 827 scope_kind = "APPLICATION" 828 if self._match_text_seq("PACKAGE"): 829 scope_kind += " PACKAGE" 830 scope = self._parse_table_parts() 831 elif self._match_set(self.DB_CREATABLES): 832 scope_kind = self._prev.text.upper() 833 if self._curr: 834 scope = self._parse_table_parts() 835 elif self._curr: 836 scope_kind = "SCHEMA" if this in self.SCHEMA_KINDS else "TABLE" 837 scope = self._parse_table_parts() 838 839 return self.expression( 840 exp.Show, 841 **{ 842 "terse": terse, 843 "this": this, 844 "history": history, 845 "like": like, 846 "scope": scope, 847 "scope_kind": scope_kind, 848 "starts_with": self._match_text_seq("STARTS", "WITH") and self._parse_string(), 849 "limit": self._parse_limit(), 850 "from": self._parse_string() if self._match(TokenType.FROM) else None, 851 "privileges": self._match_text_seq("WITH", "PRIVILEGES") 852 and self._parse_csv(lambda: self._parse_var(any_token=True, upper=True)), 853 }, 854 ) 855 856 def _parse_put(self) -> exp.Put | exp.Command: 857 if self._curr.token_type != TokenType.STRING: 858 return self._parse_as_command(self._prev) 859 860 return self.expression( 861 exp.Put, 862 this=self._parse_string(), 863 target=self._parse_location_path(), 864 properties=self._parse_properties(), 865 ) 866 867 def _parse_get(self) -> t.Optional[exp.Expression]: 868 start = self._prev 869 870 # If we detect GET( then we need to parse a function, not a statement 871 if self._match(TokenType.L_PAREN): 872 self._retreat(self._index - 2) 873 return self._parse_expression() 874 875 target = self._parse_location_path() 876 877 # Parse as command if unquoted file path 878 if self._curr.token_type == TokenType.URI_START: 879 return self._parse_as_command(start) 880 881 return self.expression( 882 exp.Get, 883 this=self._parse_string(), 884 target=target, 885 properties=self._parse_properties(), 886 ) 887 888 def _parse_location_property(self) -> exp.LocationProperty: 889 self._match(TokenType.EQ) 890 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 891 892 def _parse_file_location(self) -> t.Optional[exp.Expression]: 893 # Parse either a subquery or a staged file 894 return ( 895 self._parse_select(table=True, parse_subquery_alias=False) 896 if self._match(TokenType.L_PAREN, advance=False) 897 else self._parse_table_parts() 898 ) 899 900 def _parse_location_path(self) -> exp.Var: 901 start = self._curr 902 self._advance_any(ignore_reserved=True) 903 904 # We avoid consuming a comma token because external tables like @foo and @bar 905 # can be joined in a query with a comma separator, as well as closing paren 906 # in case of subqueries 907 while self._is_connected() and not self._match_set( 908 (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False 909 ): 910 self._advance_any(ignore_reserved=True) 911 912 return exp.var(self._find_sql(start, self._prev)) 913 914 def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: 915 this = super()._parse_lambda_arg() 916 917 if not this: 918 return this 919 920 typ = self._parse_types() 921 922 if typ: 923 return self.expression(exp.Cast, this=this, to=typ) 924 925 return this 926 927 def _parse_foreign_key(self) -> exp.ForeignKey: 928 # inlineFK, the REFERENCES columns are implied 929 if self._match(TokenType.REFERENCES, advance=False): 930 return self.expression(exp.ForeignKey) 931 932 # outoflineFK, explicitly names the columns 933 return super()._parse_foreign_key() 934 935 def _parse_file_format_property(self) -> exp.FileFormatProperty: 936 self._match(TokenType.EQ) 937 if self._match(TokenType.L_PAREN, advance=False): 938 expressions = self._parse_wrapped_options() 939 else: 940 expressions = [self._parse_format_name()] 941 942 return self.expression( 943 exp.FileFormatProperty, 944 expressions=expressions, 945 ) 946 947 def _parse_credentials_property(self) -> exp.CredentialsProperty: 948 return self.expression( 949 exp.CredentialsProperty, 950 expressions=self._parse_wrapped_options(), 951 )
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
- 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
- PIPE_SYNTAX_TRANSFORM_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
- PROCEDURE_OPTIONS
- EXECUTE_AS_OPTIONS
- KEY_CONSTRAINT_OPTIONS
- WINDOW_EXCLUDE_OPTIONS
- INSERT_ALTERNATIVES
- CLONE_KEYWORDS
- HISTORICAL_DATA_PREFIX
- HISTORICAL_DATA_KIND
- OPCLASS_FOLLOW_KEYWORDS
- OPTYPE_FOLLOW_TOKENS
- TABLE_INDEX_HINT_TOKENS
- VIEW_ATTRIBUTES
- WINDOW_ALIAS_TOKENS
- WINDOW_BEFORE_PAREN_TOKENS
- WINDOW_SIDES
- JSON_KEY_VALUE_SEPARATOR_TOKENS
- FETCH_TOKENS
- ADD_CONSTRAINT_TOKENS
- DISTINCT_TOKENS
- NULL_TOKENS
- UNNEST_OFFSET_ALIAS_TOKENS
- SELECT_START_TOKENS
- COPY_INTO_VARLEN_OPTIONS
- IS_JSON_PREDICATE_KIND
- ODBC_DATETIME_LITERALS
- ON_CONDITION_TOKENS
- PRIVILEGE_FOLLOW_TOKENS
- DESCRIBE_STYLES
- ANALYZE_STYLES
- ANALYZE_EXPRESSION_PARSERS
- PARTITION_KEYWORDS
- AMBIGUOUS_ALIAS_TOKENS
- OPERATION_MODIFIERS
- RECURSIVE_CTE_SEARCH_KIND
- MODIFIABLES
- STRICT_CAST
- PREFIXED_PIVOT_COLUMNS
- LOG_DEFAULTS_TO_LN
- 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
- WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
- OPTIONAL_ALIAS_TOKEN_CTE
- ALTER_RENAME_REQUIRES_COLUMN
- error_level
- error_message_context
- max_errors
- dialect
- reset
- parse
- parse_into
- check_errors
- raise_error
- expression
- validate_expression
- parse_set_operation
- errors
- sql
953 class Tokenizer(tokens.Tokenizer): 954 STRING_ESCAPES = ["\\", "'"] 955 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 956 RAW_STRINGS = ["$$"] 957 COMMENTS = ["--", "//", ("/*", "*/")] 958 NESTED_COMMENTS = False 959 960 KEYWORDS = { 961 **tokens.Tokenizer.KEYWORDS, 962 "FILE://": TokenType.URI_START, 963 "BYTEINT": TokenType.INT, 964 "EXCLUDE": TokenType.EXCEPT, 965 "FILE FORMAT": TokenType.FILE_FORMAT, 966 "GET": TokenType.GET, 967 "ILIKE ANY": TokenType.ILIKE_ANY, 968 "LIKE ANY": TokenType.LIKE_ANY, 969 "MATCH_CONDITION": TokenType.MATCH_CONDITION, 970 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 971 "MINUS": TokenType.EXCEPT, 972 "NCHAR VARYING": TokenType.VARCHAR, 973 "PUT": TokenType.PUT, 974 "REMOVE": TokenType.COMMAND, 975 "RM": TokenType.COMMAND, 976 "SAMPLE": TokenType.TABLE_SAMPLE, 977 "SQL_DOUBLE": TokenType.DOUBLE, 978 "SQL_VARCHAR": TokenType.VARCHAR, 979 "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION, 980 "TAG": TokenType.TAG, 981 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 982 "TOP": TokenType.TOP, 983 "WAREHOUSE": TokenType.WAREHOUSE, 984 "STAGE": TokenType.STAGE, 985 "STREAMLIT": TokenType.STREAMLIT, 986 } 987 KEYWORDS.pop("/*+") 988 989 SINGLE_TOKENS = { 990 **tokens.Tokenizer.SINGLE_TOKENS, 991 "$": TokenType.PARAMETER, 992 } 993 994 VAR_SINGLE_TOKENS = {"$"} 995 996 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- BIT_STRINGS
- BYTE_STRINGS
- HEREDOC_STRINGS
- UNICODE_STRINGS
- IDENTIFIERS
- QUOTES
- IDENTIFIER_ESCAPES
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- HINT_START
- TOKENS_PRECEDING_HINT
- WHITE_SPACE
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- dialect
- use_rs_tokenizer
- reset
- tokenize
- tokenize_rs
- size
- sql
- tokens
998 class Generator(generator.Generator): 999 PARAMETER_TOKEN = "$" 1000 MATCHED_BY_SOURCE = False 1001 SINGLE_STRING_INTERVAL = True 1002 JOIN_HINTS = False 1003 TABLE_HINTS = False 1004 QUERY_HINTS = False 1005 AGGREGATE_FILTER_SUPPORTED = False 1006 SUPPORTS_TABLE_COPY = False 1007 COLLATE_IS_FUNC = True 1008 LIMIT_ONLY_LITERALS = True 1009 JSON_KEY_VALUE_PAIR_SEP = "," 1010 INSERT_OVERWRITE = " OVERWRITE INTO" 1011 STRUCT_DELIMITER = ("(", ")") 1012 COPY_PARAMS_ARE_WRAPPED = False 1013 COPY_PARAMS_EQ_REQUIRED = True 1014 STAR_EXCEPT = "EXCLUDE" 1015 SUPPORTS_EXPLODING_PROJECTIONS = False 1016 ARRAY_CONCAT_IS_VAR_LEN = False 1017 SUPPORTS_CONVERT_TIMEZONE = True 1018 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 1019 SUPPORTS_MEDIAN = True 1020 ARRAY_SIZE_NAME = "ARRAY_SIZE" 1021 1022 TRANSFORMS = { 1023 **generator.Generator.TRANSFORMS, 1024 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 1025 exp.ArgMax: rename_func("MAX_BY"), 1026 exp.ArgMin: rename_func("MIN_BY"), 1027 exp.ArrayConcat: lambda self, e: self.arrayconcat_sql(e, name="ARRAY_CAT"), 1028 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 1029 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 1030 exp.AtTimeZone: lambda self, e: self.func( 1031 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 1032 ), 1033 exp.BitwiseOr: rename_func("BITOR"), 1034 exp.BitwiseXor: rename_func("BITXOR"), 1035 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 1036 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 1037 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 1038 exp.DateAdd: date_delta_sql("DATEADD"), 1039 exp.DateDiff: date_delta_sql("DATEDIFF"), 1040 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 1041 exp.DatetimeDiff: timestampdiff_sql, 1042 exp.DateStrToDate: datestrtodate_sql, 1043 exp.DayOfMonth: rename_func("DAYOFMONTH"), 1044 exp.DayOfWeek: rename_func("DAYOFWEEK"), 1045 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 1046 exp.DayOfYear: rename_func("DAYOFYEAR"), 1047 exp.Explode: rename_func("FLATTEN"), 1048 exp.Extract: lambda self, e: self.func( 1049 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 1050 ), 1051 exp.FileFormatProperty: lambda self, 1052 e: f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})", 1053 exp.FromTimeZone: lambda self, e: self.func( 1054 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 1055 ), 1056 exp.GenerateSeries: lambda self, e: self.func( 1057 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 1058 ), 1059 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 1060 exp.If: if_sql(name="IFF", false_value="NULL"), 1061 exp.JSONExtractArray: _json_extract_value_array_sql, 1062 exp.JSONExtractScalar: lambda self, e: self.func( 1063 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 1064 ), 1065 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 1066 exp.JSONPathRoot: lambda *_: "", 1067 exp.JSONValueArray: _json_extract_value_array_sql, 1068 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 1069 rename_func("EDITDISTANCE") 1070 ), 1071 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 1072 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 1073 exp.LogicalOr: rename_func("BOOLOR_AGG"), 1074 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 1075 exp.MakeInterval: no_make_interval_sql, 1076 exp.Max: max_or_greatest, 1077 exp.Min: min_or_least, 1078 exp.ParseJSON: lambda self, e: self.func( 1079 "TRY_PARSE_JSON" if e.args.get("safe") else "PARSE_JSON", e.this 1080 ), 1081 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 1082 exp.PercentileCont: transforms.preprocess( 1083 [transforms.add_within_group_for_percentiles] 1084 ), 1085 exp.PercentileDisc: transforms.preprocess( 1086 [transforms.add_within_group_for_percentiles] 1087 ), 1088 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 1089 exp.RegexpExtract: _regexpextract_sql, 1090 exp.RegexpExtractAll: _regexpextract_sql, 1091 exp.RegexpILike: _regexpilike_sql, 1092 exp.Rand: rename_func("RANDOM"), 1093 exp.Select: transforms.preprocess( 1094 [ 1095 transforms.eliminate_window_clause, 1096 transforms.eliminate_distinct_on, 1097 transforms.explode_projection_to_unnest(), 1098 transforms.eliminate_semi_and_anti_joins, 1099 _transform_generate_date_array, 1100 ] 1101 ), 1102 exp.SHA: rename_func("SHA1"), 1103 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 1104 exp.StartsWith: rename_func("STARTSWITH"), 1105 exp.EndsWith: rename_func("ENDSWITH"), 1106 exp.StrPosition: lambda self, e: strposition_sql( 1107 self, e, func_name="CHARINDEX", supports_position=True 1108 ), 1109 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 1110 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 1111 exp.Stuff: rename_func("INSERT"), 1112 exp.StPoint: rename_func("ST_MAKEPOINT"), 1113 exp.TimeAdd: date_delta_sql("TIMEADD"), 1114 exp.Timestamp: no_timestamp_sql, 1115 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 1116 exp.TimestampDiff: lambda self, e: self.func( 1117 "TIMESTAMPDIFF", e.unit, e.expression, e.this 1118 ), 1119 exp.TimestampTrunc: timestamptrunc_sql(), 1120 exp.TimeStrToTime: timestrtotime_sql, 1121 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 1122 exp.ToArray: rename_func("TO_ARRAY"), 1123 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 1124 exp.ToDouble: rename_func("TO_DOUBLE"), 1125 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 1126 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 1127 exp.TsOrDsToDate: lambda self, e: self.func( 1128 "TRY_TO_DATE" if e.args.get("safe") else "TO_DATE", e.this, self.format_time(e) 1129 ), 1130 exp.TsOrDsToTime: lambda self, e: self.func( 1131 "TRY_TO_TIME" if e.args.get("safe") else "TO_TIME", e.this, self.format_time(e) 1132 ), 1133 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 1134 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 1135 exp.Uuid: rename_func("UUID_STRING"), 1136 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 1137 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 1138 exp.Xor: rename_func("BOOLXOR"), 1139 } 1140 1141 SUPPORTED_JSON_PATH_PARTS = { 1142 exp.JSONPathKey, 1143 exp.JSONPathRoot, 1144 exp.JSONPathSubscript, 1145 } 1146 1147 TYPE_MAPPING = { 1148 **generator.Generator.TYPE_MAPPING, 1149 exp.DataType.Type.NESTED: "OBJECT", 1150 exp.DataType.Type.STRUCT: "OBJECT", 1151 exp.DataType.Type.BIGDECIMAL: "DOUBLE", 1152 } 1153 1154 TOKEN_MAPPING = { 1155 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 1156 } 1157 1158 PROPERTIES_LOCATION = { 1159 **generator.Generator.PROPERTIES_LOCATION, 1160 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 1161 exp.LocationProperty: exp.Properties.Location.POST_WITH, 1162 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 1163 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 1164 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 1165 } 1166 1167 UNSUPPORTED_VALUES_EXPRESSIONS = { 1168 exp.Map, 1169 exp.StarMap, 1170 exp.Struct, 1171 exp.VarMap, 1172 } 1173 1174 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 1175 1176 def with_properties(self, properties: exp.Properties) -> str: 1177 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 1178 1179 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 1180 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 1181 values_as_table = False 1182 1183 return super().values_sql(expression, values_as_table=values_as_table) 1184 1185 def datatype_sql(self, expression: exp.DataType) -> str: 1186 expressions = expression.expressions 1187 if ( 1188 expressions 1189 and expression.is_type(*exp.DataType.STRUCT_TYPES) 1190 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 1191 ): 1192 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 1193 return "OBJECT" 1194 1195 return super().datatype_sql(expression) 1196 1197 def tonumber_sql(self, expression: exp.ToNumber) -> str: 1198 return self.func( 1199 "TO_NUMBER", 1200 expression.this, 1201 expression.args.get("format"), 1202 expression.args.get("precision"), 1203 expression.args.get("scale"), 1204 ) 1205 1206 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 1207 milli = expression.args.get("milli") 1208 if milli is not None: 1209 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 1210 expression.set("nano", milli_to_nano) 1211 1212 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 1213 1214 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1215 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 1216 return self.func("TO_GEOGRAPHY", expression.this) 1217 if expression.is_type(exp.DataType.Type.GEOMETRY): 1218 return self.func("TO_GEOMETRY", expression.this) 1219 1220 return super().cast_sql(expression, safe_prefix=safe_prefix) 1221 1222 def trycast_sql(self, expression: exp.TryCast) -> str: 1223 value = expression.this 1224 1225 if value.type is None: 1226 from sqlglot.optimizer.annotate_types import annotate_types 1227 1228 value = annotate_types(value, dialect=self.dialect) 1229 1230 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 1231 return super().trycast_sql(expression) 1232 1233 # TRY_CAST only works for string values in Snowflake 1234 return self.cast_sql(expression) 1235 1236 def log_sql(self, expression: exp.Log) -> str: 1237 if not expression.expression: 1238 return self.func("LN", expression.this) 1239 1240 return super().log_sql(expression) 1241 1242 def unnest_sql(self, expression: exp.Unnest) -> str: 1243 unnest_alias = expression.args.get("alias") 1244 offset = expression.args.get("offset") 1245 1246 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 1247 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 1248 1249 columns = [ 1250 exp.to_identifier("seq"), 1251 exp.to_identifier("key"), 1252 exp.to_identifier("path"), 1253 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 1254 value, 1255 exp.to_identifier("this"), 1256 ] 1257 1258 if unnest_alias: 1259 unnest_alias.set("columns", columns) 1260 else: 1261 unnest_alias = exp.TableAlias(this="_u", columns=columns) 1262 1263 table_input = self.sql(expression.expressions[0]) 1264 if not table_input.startswith("INPUT =>"): 1265 table_input = f"INPUT => {table_input}" 1266 1267 explode = f"TABLE(FLATTEN({table_input}))" 1268 alias = self.sql(unnest_alias) 1269 alias = f" AS {alias}" if alias else "" 1270 value = "" if isinstance(expression.parent, (exp.From, exp.Join)) else f"{value} FROM " 1271 1272 return f"{value}{explode}{alias}" 1273 1274 def show_sql(self, expression: exp.Show) -> str: 1275 terse = "TERSE " if expression.args.get("terse") else "" 1276 history = " HISTORY" if expression.args.get("history") else "" 1277 like = self.sql(expression, "like") 1278 like = f" LIKE {like}" if like else "" 1279 1280 scope = self.sql(expression, "scope") 1281 scope = f" {scope}" if scope else "" 1282 1283 scope_kind = self.sql(expression, "scope_kind") 1284 if scope_kind: 1285 scope_kind = f" IN {scope_kind}" 1286 1287 starts_with = self.sql(expression, "starts_with") 1288 if starts_with: 1289 starts_with = f" STARTS WITH {starts_with}" 1290 1291 limit = self.sql(expression, "limit") 1292 1293 from_ = self.sql(expression, "from") 1294 if from_: 1295 from_ = f" FROM {from_}" 1296 1297 privileges = self.expressions(expression, key="privileges", flat=True) 1298 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 1299 1300 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 1301 1302 def describe_sql(self, expression: exp.Describe) -> str: 1303 # Default to table if kind is unknown 1304 kind_value = expression.args.get("kind") or "TABLE" 1305 kind = f" {kind_value}" if kind_value else "" 1306 this = f" {self.sql(expression, 'this')}" 1307 expressions = self.expressions(expression, flat=True) 1308 expressions = f" {expressions}" if expressions else "" 1309 return f"DESCRIBE{kind}{this}{expressions}" 1310 1311 def generatedasidentitycolumnconstraint_sql( 1312 self, expression: exp.GeneratedAsIdentityColumnConstraint 1313 ) -> str: 1314 start = expression.args.get("start") 1315 start = f" START {start}" if start else "" 1316 increment = expression.args.get("increment") 1317 increment = f" INCREMENT {increment}" if increment else "" 1318 return f"AUTOINCREMENT{start}{increment}" 1319 1320 def cluster_sql(self, expression: exp.Cluster) -> str: 1321 return f"CLUSTER BY ({self.expressions(expression, flat=True)})" 1322 1323 def struct_sql(self, expression: exp.Struct) -> str: 1324 keys = [] 1325 values = [] 1326 1327 for i, e in enumerate(expression.expressions): 1328 if isinstance(e, exp.PropertyEQ): 1329 keys.append( 1330 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1331 ) 1332 values.append(e.expression) 1333 else: 1334 keys.append(exp.Literal.string(f"_{i}")) 1335 values.append(e) 1336 1337 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 1338 1339 @unsupported_args("weight", "accuracy") 1340 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 1341 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 1342 1343 def alterset_sql(self, expression: exp.AlterSet) -> str: 1344 exprs = self.expressions(expression, flat=True) 1345 exprs = f" {exprs}" if exprs else "" 1346 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1347 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1348 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1349 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1350 tag = self.expressions(expression, key="tag", flat=True) 1351 tag = f" TAG {tag}" if tag else "" 1352 1353 return f"SET{exprs}{file_format}{copy_options}{tag}" 1354 1355 def strtotime_sql(self, expression: exp.StrToTime): 1356 safe_prefix = "TRY_" if expression.args.get("safe") else "" 1357 return self.func( 1358 f"{safe_prefix}TO_TIMESTAMP", expression.this, self.format_time(expression) 1359 ) 1360 1361 def timestampsub_sql(self, expression: exp.TimestampSub): 1362 return self.sql( 1363 exp.TimestampAdd( 1364 this=expression.this, 1365 expression=expression.expression * -1, 1366 unit=expression.unit, 1367 ) 1368 ) 1369 1370 def jsonextract_sql(self, expression: exp.JSONExtract): 1371 this = expression.this 1372 1373 # JSON strings are valid coming from other dialects such as BQ 1374 return self.func( 1375 "GET_PATH", 1376 exp.ParseJSON(this=this) if this.is_string else this, 1377 expression.expression, 1378 ) 1379 1380 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 1381 this = expression.this 1382 if not isinstance(this, exp.TsOrDsToTimestamp): 1383 this = exp.cast(this, exp.DataType.Type.TIMESTAMP) 1384 1385 return self.func("TO_CHAR", this, self.format_time(expression)) 1386 1387 def datesub_sql(self, expression: exp.DateSub) -> str: 1388 value = expression.expression 1389 if value: 1390 value.replace(value * (-1)) 1391 else: 1392 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1393 1394 return date_delta_sql("DATEADD")(self, expression) 1395 1396 def select_sql(self, expression: exp.Select) -> str: 1397 limit = expression.args.get("limit") 1398 offset = expression.args.get("offset") 1399 if offset and not limit: 1400 expression.limit(exp.Null(), copy=False) 1401 return super().select_sql(expression) 1402 1403 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1404 is_materialized = expression.find(exp.MaterializedProperty) 1405 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1406 1407 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1408 # For materialized views, COPY GRANTS is located *before* the columns list 1409 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1410 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1411 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1412 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1413 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1414 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1415 1416 this_name = self.sql(expression.this, "this") 1417 copy_grants = self.sql(copy_grants_property) 1418 this_schema = self.schema_columns_sql(expression.this) 1419 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1420 1421 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1422 1423 return super().createable_sql(expression, locations) 1424 1425 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1426 this = expression.this 1427 1428 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1429 # and add it later as part of the WITHIN GROUP clause 1430 order = this if isinstance(this, exp.Order) else None 1431 if order: 1432 expression.set("this", order.this.pop()) 1433 1434 expr_sql = super().arrayagg_sql(expression) 1435 1436 if order: 1437 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1438 1439 return expr_sql 1440 1441 def array_sql(self, expression: exp.Array) -> str: 1442 expressions = expression.expressions 1443 1444 first_expr = seq_get(expressions, 0) 1445 if isinstance(first_expr, exp.Select): 1446 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1447 if first_expr.text("kind").upper() == "STRUCT": 1448 object_construct_args = [] 1449 for expr in first_expr.expressions: 1450 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1451 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1452 name = expr.this if isinstance(expr, exp.Alias) else expr 1453 1454 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1455 1456 array_agg = exp.ArrayAgg( 1457 this=_build_object_construct(args=object_construct_args) 1458 ) 1459 1460 first_expr.set("kind", None) 1461 first_expr.set("expressions", [array_agg]) 1462 1463 return self.sql(first_expr.subquery()) 1464 1465 return inline_array_sql(self, expression)
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
1185 def datatype_sql(self, expression: exp.DataType) -> str: 1186 expressions = expression.expressions 1187 if ( 1188 expressions 1189 and expression.is_type(*exp.DataType.STRUCT_TYPES) 1190 and any(isinstance(field_type, exp.DataType) for field_type in expressions) 1191 ): 1192 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 1193 return "OBJECT" 1194 1195 return super().datatype_sql(expression)
1206 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 1207 milli = expression.args.get("milli") 1208 if milli is not None: 1209 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 1210 expression.set("nano", milli_to_nano) 1211 1212 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
1214 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 1215 if expression.is_type(exp.DataType.Type.GEOGRAPHY): 1216 return self.func("TO_GEOGRAPHY", expression.this) 1217 if expression.is_type(exp.DataType.Type.GEOMETRY): 1218 return self.func("TO_GEOMETRY", expression.this) 1219 1220 return super().cast_sql(expression, safe_prefix=safe_prefix)
1222 def trycast_sql(self, expression: exp.TryCast) -> str: 1223 value = expression.this 1224 1225 if value.type is None: 1226 from sqlglot.optimizer.annotate_types import annotate_types 1227 1228 value = annotate_types(value, dialect=self.dialect) 1229 1230 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 1231 return super().trycast_sql(expression) 1232 1233 # TRY_CAST only works for string values in Snowflake 1234 return self.cast_sql(expression)
1242 def unnest_sql(self, expression: exp.Unnest) -> str: 1243 unnest_alias = expression.args.get("alias") 1244 offset = expression.args.get("offset") 1245 1246 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 1247 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 1248 1249 columns = [ 1250 exp.to_identifier("seq"), 1251 exp.to_identifier("key"), 1252 exp.to_identifier("path"), 1253 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 1254 value, 1255 exp.to_identifier("this"), 1256 ] 1257 1258 if unnest_alias: 1259 unnest_alias.set("columns", columns) 1260 else: 1261 unnest_alias = exp.TableAlias(this="_u", columns=columns) 1262 1263 table_input = self.sql(expression.expressions[0]) 1264 if not table_input.startswith("INPUT =>"): 1265 table_input = f"INPUT => {table_input}" 1266 1267 explode = f"TABLE(FLATTEN({table_input}))" 1268 alias = self.sql(unnest_alias) 1269 alias = f" AS {alias}" if alias else "" 1270 value = "" if isinstance(expression.parent, (exp.From, exp.Join)) else f"{value} FROM " 1271 1272 return f"{value}{explode}{alias}"
1274 def show_sql(self, expression: exp.Show) -> str: 1275 terse = "TERSE " if expression.args.get("terse") else "" 1276 history = " HISTORY" if expression.args.get("history") else "" 1277 like = self.sql(expression, "like") 1278 like = f" LIKE {like}" if like else "" 1279 1280 scope = self.sql(expression, "scope") 1281 scope = f" {scope}" if scope else "" 1282 1283 scope_kind = self.sql(expression, "scope_kind") 1284 if scope_kind: 1285 scope_kind = f" IN {scope_kind}" 1286 1287 starts_with = self.sql(expression, "starts_with") 1288 if starts_with: 1289 starts_with = f" STARTS WITH {starts_with}" 1290 1291 limit = self.sql(expression, "limit") 1292 1293 from_ = self.sql(expression, "from") 1294 if from_: 1295 from_ = f" FROM {from_}" 1296 1297 privileges = self.expressions(expression, key="privileges", flat=True) 1298 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 1299 1300 return f"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}"
1302 def describe_sql(self, expression: exp.Describe) -> str: 1303 # Default to table if kind is unknown 1304 kind_value = expression.args.get("kind") or "TABLE" 1305 kind = f" {kind_value}" if kind_value else "" 1306 this = f" {self.sql(expression, 'this')}" 1307 expressions = self.expressions(expression, flat=True) 1308 expressions = f" {expressions}" if expressions else "" 1309 return f"DESCRIBE{kind}{this}{expressions}"
1311 def generatedasidentitycolumnconstraint_sql( 1312 self, expression: exp.GeneratedAsIdentityColumnConstraint 1313 ) -> str: 1314 start = expression.args.get("start") 1315 start = f" START {start}" if start else "" 1316 increment = expression.args.get("increment") 1317 increment = f" INCREMENT {increment}" if increment else "" 1318 return f"AUTOINCREMENT{start}{increment}"
1323 def struct_sql(self, expression: exp.Struct) -> str: 1324 keys = [] 1325 values = [] 1326 1327 for i, e in enumerate(expression.expressions): 1328 if isinstance(e, exp.PropertyEQ): 1329 keys.append( 1330 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 1331 ) 1332 values.append(e.expression) 1333 else: 1334 keys.append(exp.Literal.string(f"_{i}")) 1335 values.append(e) 1336 1337 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
1343 def alterset_sql(self, expression: exp.AlterSet) -> str: 1344 exprs = self.expressions(expression, flat=True) 1345 exprs = f" {exprs}" if exprs else "" 1346 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 1347 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 1348 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 1349 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 1350 tag = self.expressions(expression, key="tag", flat=True) 1351 tag = f" TAG {tag}" if tag else "" 1352 1353 return f"SET{exprs}{file_format}{copy_options}{tag}"
1387 def datesub_sql(self, expression: exp.DateSub) -> str: 1388 value = expression.expression 1389 if value: 1390 value.replace(value * (-1)) 1391 else: 1392 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1393 1394 return date_delta_sql("DATEADD")(self, expression)
1403 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1404 is_materialized = expression.find(exp.MaterializedProperty) 1405 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1406 1407 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1408 # For materialized views, COPY GRANTS is located *before* the columns list 1409 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1410 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1411 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1412 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1413 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1414 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1415 1416 this_name = self.sql(expression.this, "this") 1417 copy_grants = self.sql(copy_grants_property) 1418 this_schema = self.schema_columns_sql(expression.this) 1419 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1420 1421 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1422 1423 return super().createable_sql(expression, locations)
1425 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1426 this = expression.this 1427 1428 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1429 # and add it later as part of the WITHIN GROUP clause 1430 order = this if isinstance(this, exp.Order) else None 1431 if order: 1432 expression.set("this", order.this.pop()) 1433 1434 expr_sql = super().arrayagg_sql(expression) 1435 1436 if order: 1437 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1438 1439 return expr_sql
1441 def array_sql(self, expression: exp.Array) -> str: 1442 expressions = expression.expressions 1443 1444 first_expr = seq_get(expressions, 0) 1445 if isinstance(first_expr, exp.Select): 1446 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1447 if first_expr.text("kind").upper() == "STRUCT": 1448 object_construct_args = [] 1449 for expr in first_expr.expressions: 1450 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1451 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1452 name = expr.this if isinstance(expr, exp.Alias) else expr 1453 1454 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1455 1456 array_agg = exp.ArrayAgg( 1457 this=_build_object_construct(args=object_construct_args) 1458 ) 1459 1460 first_expr.set("kind", None) 1461 first_expr.set("expressions", [array_agg]) 1462 1463 return self.sql(first_expr.subquery()) 1464 1465 return inline_array_sql(self, expression)
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
- SUPPORTS_WINDOW_EXCLUDE
- SET_OP_MODIFIERS
- COPY_HAS_INTO_KEYWORD
- HEX_FUNC
- WITH_PROPERTIES_PREFIX
- QUOTE_JSON_PATH
- PAD_FILL_PATTERN_IS_REQUIRED
- SUPPORTS_UNIX_SECONDS
- ALTER_SET_WRAPPED
- PARSE_JSON_NAME
- ALTER_SET_TYPE
- ARRAY_SIZE_DIM_REQUIRED
- TIME_PART_SINGULARS
- NAMED_PLACEHOLDER_TOKEN
- EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
- 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
- sanitize_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
- primarykeycolumnconstraint_sql
- uniquecolumnconstraint_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
- limitoptions_sql
- filter_sql
- hint_sql
- indexparameters_sql
- index_sql
- identifier_sql
- hex_sql
- lowerhex_sql
- inputoutputformat_sql
- national_sql
- partition_sql
- properties_sql
- root_properties
- properties
- locate_properties
- property_name
- property_sql
- likeproperty_sql
- fallbackproperty_sql
- journalproperty_sql
- freespaceproperty_sql
- checksumproperty_sql
- mergeblockratioproperty_sql
- datablocksizeproperty_sql
- blockcompressionproperty_sql
- isolatedloadingproperty_sql
- partitionboundspec_sql
- partitionedofproperty_sql
- lockingproperty_sql
- withdataproperty_sql
- withsystemversioningproperty_sql
- insert_sql
- introducer_sql
- kill_sql
- pseudotype_sql
- objectidentifier_sql
- onconflict_sql
- returning_sql
- rowformatdelimitedproperty_sql
- withtablehint_sql
- indextablehint_sql
- historicaldata_sql
- table_parts
- table_sql
- tablefromrows_sql
- tablesample_sql
- pivot_sql
- version_sql
- tuple_sql
- update_sql
- 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
- for_modifiers
- queryoption_sql
- offset_limit_modifiers
- after_limit_modifiers
- 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
- alterindex_sql
- alterdiststyle_sql
- altersortkey_sql
- alterrename_sql
- renamecolumn_sql
- alter_sql
- add_column_sql
- droppartition_sql
- addconstraint_sql
- addpartition_sql
- distinct_sql
- ignorenulls_sql
- respectnulls_sql
- havingmax_sql
- intdiv_sql
- dpipe_sql
- div_sql
- safedivide_sql
- overlaps_sql
- distance_sql
- dot_sql
- eq_sql
- propertyeq_sql
- escape_sql
- glob_sql
- gt_sql
- gte_sql
- 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
- jsoncast_sql
- try_sql
- use_sql
- binary
- ceil_floor
- function_fallback_sql
- func
- format_args
- too_wide
- format_time
- expressions
- op_expressions
- naked_property
- tag_sql
- token_sql
- userdefinedfunction_sql
- joinhint_sql
- kwarg_sql
- when_sql
- whens_sql
- merge_sql
- tochar_sql
- dictproperty_sql
- dictrange_sql
- dictsubproperty_sql
- duplicatekeyproperty_sql
- uniquekeyproperty_sql
- distributedbyproperty_sql
- oncluster_sql
- clusteredbyproperty_sql
- anyvalue_sql
- querytransform_sql
- indexconstraintoption_sql
- checkcolumnconstraint_sql
- indexcolumnconstraint_sql
- nvl2_sql
- comprehension_sql
- columnprefix_sql
- opclass_sql
- predict_sql
- forin_sql
- refresh_sql
- toarray_sql
- tsordstotime_sql
- tsordstotimestamp_sql
- tsordstodatetime_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
- jsonextractquote_sql
- jsonexists_sql
- apply_sql
- grant_sql
- grantprivilege_sql
- grantprincipal_sql
- columns_sql
- overlay_sql
- todouble_sql
- string_sql
- median_sql
- overflowtruncatebehavior_sql
- unixseconds_sql
- arraysize_sql
- attach_sql
- detach_sql
- attachoption_sql
- featuresattime_sql
- watermarkcolumnconstraint_sql
- encodeproperty_sql
- includeproperty_sql
- xmlelement_sql
- xmlkeyvalueoption_sql
- partitionbyrangeproperty_sql
- partitionbyrangepropertydynamic_sql
- unpivotcolumns_sql
- analyzesample_sql
- analyzestatistics_sql
- analyzehistogram_sql
- analyzedelete_sql
- analyzelistchainedrows_sql
- analyzevalidate_sql
- analyze_sql
- xmltable_sql
- xmlnamespace_sql
- export_sql
- declare_sql
- declareitem_sql
- recursivewithsearch_sql
- parameterizedagg_sql
- anonymousaggfunc_sql
- combinedaggfunc_sql
- combinedparameterizedagg_sql
- get_put_sql
- translatecharacters_sql