sqlglot.generators.snowflake
1from __future__ import annotations 2 3import typing as t 4from collections import defaultdict 5 6from sqlglot import exp, generator, transforms 7from sqlglot.dialects.dialect import ( 8 array_append_sql, 9 array_concat_sql, 10 date_delta_sql, 11 datestrtodate_sql, 12 groupconcat_sql, 13 if_sql, 14 inline_array_sql, 15 map_date_part, 16 max_or_greatest, 17 min_or_least, 18 no_make_interval_sql, 19 no_timestamp_sql, 20 rename_func, 21 strposition_sql, 22 timestampdiff_sql, 23 timestamptrunc_sql, 24 timestrtotime_sql, 25 unit_to_str, 26 var_map_sql, 27) 28from sqlglot.generator import unsupported_args 29from sqlglot.helper import find_new_name, flatten, seq_get 30from sqlglot.optimizer.scope import build_scope, find_all_in_scope 31from sqlglot.parsers.snowflake import ( 32 RANKING_WINDOW_FUNCTIONS_WITH_FRAME, 33 TIMESTAMP_TYPES, 34 SnowflakeParser, 35 build_object_construct, 36) 37from sqlglot.tokens import TokenType 38 39if t.TYPE_CHECKING: 40 from sqlglot._typing import E 41 42 43def _build_datediff(args: list) -> exp.DateDiff: 44 return exp.DateDiff( 45 this=seq_get(args, 2), 46 expression=seq_get(args, 1), 47 unit=map_date_part(seq_get(args, 0)), 48 date_part_boundary=True, 49 ) 50 51 52def _build_date_time_add(expr_type: type[E]) -> t.Callable[[list], E]: 53 def _builder(args: list) -> E: 54 return expr_type( 55 this=seq_get(args, 2), 56 expression=seq_get(args, 1), 57 unit=map_date_part(seq_get(args, 0)), 58 ) 59 60 return _builder 61 62 63def _regexpilike_sql(self: SnowflakeGenerator, expression: exp.RegexpILike) -> str: 64 flag = expression.text("flag") 65 66 if "i" not in flag: 67 flag += "i" 68 69 return self.func( 70 "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag) 71 ) 72 73 74def _unqualify_pivot_columns(expression: exp.Expr) -> exp.Expr: 75 """ 76 Snowflake doesn't allow columns referenced in UNPIVOT to be qualified, 77 so we need to unqualify them. Same goes for ANY ORDER BY <column>. 78 79 Example: 80 >>> from sqlglot import parse_one 81 >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))") 82 >>> print(_unqualify_pivot_columns(expr).sql(dialect="snowflake")) 83 SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april)) 84 """ 85 if isinstance(expression, exp.Pivot): 86 if expression.unpivot: 87 expression = transforms.unqualify_columns(expression) 88 else: 89 for field in expression.fields: 90 field_expr = seq_get(field.expressions if field else [], 0) 91 92 if isinstance(field_expr, exp.PivotAny): 93 unqualified_field_expr = transforms.unqualify_columns(field_expr) 94 t.cast(exp.Expr, field).set("expressions", unqualified_field_expr, 0) 95 96 return expression 97 98 99def _flatten_structured_types_unless_iceberg(expression: exp.Expr) -> exp.Expr: 100 assert isinstance(expression, exp.Create) 101 102 def _flatten_structured_type(expression: exp.Expr) -> exp.Expr: 103 if isinstance(expression, exp.DataType) and expression.this in exp.DataType.NESTED_TYPES: 104 expression.set("expressions", None) 105 return expression 106 107 props = expression.args.get("properties") 108 if isinstance(expression.this, exp.Schema) and not (props and props.find(exp.IcebergProperty)): 109 for schema_expression in expression.this.expressions: 110 if isinstance(schema_expression, exp.ColumnDef): 111 column_type = schema_expression.kind 112 if isinstance(column_type, exp.DataType): 113 column_type.transform(_flatten_structured_type, copy=False) 114 115 return expression 116 117 118def _unnest_generate_date_array(unnest: exp.Unnest) -> None: 119 generate_date_array = unnest.expressions[0] 120 start = generate_date_array.args.get("start") 121 end = generate_date_array.args.get("end") 122 step = generate_date_array.args.get("step") 123 124 if not start or not end or not isinstance(step, exp.Interval) or step.name != "1": 125 return 126 127 unit = step.args.get("unit") 128 129 unnest_alias = unnest.args.get("alias") 130 if unnest_alias: 131 unnest_alias = unnest_alias.copy() 132 sequence_value_name = seq_get(unnest_alias.columns, 0) or "value" 133 else: 134 sequence_value_name = "value" 135 136 # We'll add the next sequence value to the starting date and project the result 137 date_add = _build_date_time_add(exp.DateAdd)( 138 [unit, exp.cast(sequence_value_name, "int"), exp.cast(start, "date")] 139 ) 140 141 # We use DATEDIFF to compute the number of sequence values needed 142 number_sequence = SnowflakeParser.FUNCTIONS["ARRAY_GENERATE_RANGE"]( 143 [exp.Literal.number(0), _build_datediff([unit, start, end]) + 1] 144 ) 145 146 unnest.set("expressions", [number_sequence]) 147 148 unnest_parent = unnest.parent 149 if isinstance(unnest_parent, exp.Join): 150 select = unnest_parent.parent 151 if isinstance(select, exp.Select): 152 replace_column_name = ( 153 sequence_value_name 154 if isinstance(sequence_value_name, str) 155 else sequence_value_name.name 156 ) 157 158 scope = build_scope(select) 159 if scope: 160 for column in scope.columns: 161 if column.name.lower() == replace_column_name.lower(): 162 column.replace( 163 date_add.as_(replace_column_name) 164 if isinstance(column.parent, exp.Select) 165 else date_add 166 ) 167 168 lateral = exp.Lateral(this=unnest_parent.this.pop()) 169 unnest_parent.replace(exp.Join(this=lateral)) 170 else: 171 unnest.replace( 172 exp.select(date_add.as_(sequence_value_name)) 173 .from_(unnest.copy()) 174 .subquery(unnest_alias) 175 ) 176 177 178def _transform_generate_date_array(expression: exp.Expr) -> exp.Expr: 179 if isinstance(expression, exp.Select): 180 for generate_date_array in expression.find_all(exp.GenerateDateArray): 181 parent = generate_date_array.parent 182 183 # If GENERATE_DATE_ARRAY is used directly as an array (e.g passed into ARRAY_LENGTH), the transformed Snowflake 184 # query is the following (it'll be unnested properly on the next iteration due to copy): 185 # SELECT ref(GENERATE_DATE_ARRAY(...)) -> SELECT ref((SELECT ARRAY_AGG(*) FROM UNNEST(GENERATE_DATE_ARRAY(...)))) 186 if not isinstance(parent, exp.Unnest): 187 unnest = exp.Unnest(expressions=[generate_date_array.copy()]) 188 generate_date_array.replace( 189 exp.select(exp.ArrayAgg(this=exp.Star())).from_(unnest).subquery() 190 ) 191 192 if ( 193 isinstance(parent, exp.Unnest) 194 and isinstance(parent.parent, (exp.From, exp.Join)) 195 and len(parent.expressions) == 1 196 ): 197 _unnest_generate_date_array(parent) 198 199 return expression 200 201 202def _regexpextract_sql( 203 self: SnowflakeGenerator, expression: exp.RegexpExtract | exp.RegexpExtractAll 204) -> str: 205 # Other dialects don't support all of the following parameters, so we need to 206 # generate default values as necessary to ensure the transpilation is correct 207 group = expression.args.get("group") 208 209 # To avoid generating all these default values, we set group to None if 210 # it's 0 (also default value) which doesn't trigger the following chain 211 if group and group.name == "0": 212 group = None 213 214 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 215 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 216 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 217 218 return self.func( 219 "REGEXP_SUBSTR" if isinstance(expression, exp.RegexpExtract) else "REGEXP_SUBSTR_ALL", 220 expression.this, 221 expression.expression, 222 position, 223 occurrence, 224 parameters, 225 group, 226 ) 227 228 229def _json_extract_value_array_sql( 230 self: SnowflakeGenerator, expression: exp.JSONValueArray | exp.JSONExtractArray 231) -> str: 232 json_extract = exp.JSONExtract(this=expression.this, expression=expression.expression) 233 ident = exp.to_identifier("x") 234 235 if isinstance(expression, exp.JSONValueArray): 236 this: exp.Expr = exp.cast(ident, to=exp.DType.VARCHAR) 237 else: 238 this = exp.ParseJSON(this=f"TO_JSON({ident})") 239 240 transform_lambda = exp.Lambda(expressions=[ident], this=this) 241 242 return self.func("TRANSFORM", json_extract, transform_lambda) 243 244 245def _qualify_unnested_columns(expression: exp.Expr) -> exp.Expr: 246 if isinstance(expression, exp.Select): 247 scope = build_scope(expression) 248 if not scope: 249 return expression 250 251 unnests = list(scope.find_all(exp.Unnest)) 252 253 if not unnests: 254 return expression 255 256 taken_source_names = set(scope.sources) 257 column_source: dict[str, exp.Identifier] = {} 258 unnest_to_identifier: dict[exp.Unnest, exp.Identifier] = {} 259 260 unnest_identifier: exp.Identifier | None = None 261 orig_expression = expression.copy() 262 263 for unnest in unnests: 264 if not isinstance(unnest.parent, (exp.From, exp.Join)): 265 continue 266 267 # Try to infer column names produced by an unnest operator. This is only possible 268 # when we can peek into the (statically known) contents of the unnested value. 269 unnest_columns: set[str] = set() 270 for unnest_expr in unnest.expressions: 271 if not isinstance(unnest_expr, exp.Array): 272 continue 273 274 for array_expr in unnest_expr.expressions: 275 if not ( 276 isinstance(array_expr, exp.Struct) 277 and array_expr.expressions 278 and all( 279 isinstance(struct_expr, exp.PropertyEQ) 280 for struct_expr in array_expr.expressions 281 ) 282 ): 283 continue 284 285 unnest_columns.update( 286 struct_expr.this.name.lower() for struct_expr in array_expr.expressions 287 ) 288 break 289 290 if unnest_columns: 291 break 292 293 unnest_alias = unnest.args.get("alias") 294 if not unnest_alias: 295 alias_name = find_new_name(taken_source_names, "value") 296 taken_source_names.add(alias_name) 297 298 # Produce a `TableAlias` AST similar to what is produced for BigQuery. This 299 # will be corrected later, when we generate SQL for the `Unnest` AST node. 300 aliased_unnest = exp.alias_(unnest, None, table=[alias_name]) 301 scope.replace(unnest, aliased_unnest) 302 303 unnest_identifier = aliased_unnest.args["alias"].columns[0] 304 else: 305 alias_columns = getattr(unnest_alias, "columns", []) 306 unnest_identifier = unnest_alias.this or seq_get(alias_columns, 0) 307 308 if not isinstance(unnest_identifier, exp.Identifier): 309 return orig_expression 310 311 unnest_to_identifier[unnest] = unnest_identifier 312 column_source.update({c.lower(): unnest_identifier for c in unnest_columns}) 313 314 for column in scope.columns: 315 if column.table: 316 continue 317 318 table = column_source.get(column.name.lower()) 319 if ( 320 unnest_identifier 321 and not table 322 and len(scope.sources) == 1 323 and column.name.lower() != unnest_identifier.name.lower() 324 ): 325 unnest_ancestor = column.find_ancestor(exp.Unnest, exp.Select) 326 if isinstance(unnest_ancestor, exp.Unnest): 327 ancestor_identifier = unnest_to_identifier.get(unnest_ancestor) 328 if ( 329 ancestor_identifier 330 and ancestor_identifier.name.lower() == unnest_identifier.name.lower() 331 ): 332 continue 333 334 table = unnest_identifier 335 336 column.set("table", table and table.copy()) 337 338 return expression 339 340 341def _eliminate_dot_variant_lookup(expression: exp.Expr) -> exp.Expr: 342 if isinstance(expression, exp.Select): 343 # This transformation is used to facilitate transpilation of BigQuery `UNNEST` operations 344 # to Snowflake. It should not affect roundtrip because `Unnest` nodes cannot be produced 345 # by Snowflake's parser. 346 # 347 # Additionally, at the time of writing this, BigQuery is the only dialect that produces a 348 # `TableAlias` node that only fills `columns` and not `this`, due to `UNNEST_COLUMN_ONLY`. 349 unnest_aliases = set() 350 for unnest in find_all_in_scope(expression, exp.Unnest): 351 unnest_alias = unnest.args.get("alias") 352 if ( 353 isinstance(unnest_alias, exp.TableAlias) 354 and not unnest_alias.this 355 and len(unnest_alias.columns) == 1 356 ): 357 unnest_aliases.add(unnest_alias.columns[0].name) 358 359 if unnest_aliases: 360 for c in find_all_in_scope(expression, exp.Column): 361 if c.table in unnest_aliases: 362 bracket_lhs = c.args["table"] 363 bracket_rhs = exp.Literal.string(c.name) 364 bracket = exp.Bracket(this=bracket_lhs, expressions=[bracket_rhs]) 365 366 if c.parent is expression: 367 # Retain column projection names by using aliases 368 c.replace(exp.alias_(bracket, c.this.copy())) 369 else: 370 c.replace(bracket) 371 372 return expression 373 374 375class SnowflakeGenerator(generator.Generator): 376 SELECT_KINDS: tuple[str, ...] = () 377 PARAMETER_TOKEN = "$" 378 MATCHED_BY_SOURCE = False 379 SINGLE_STRING_INTERVAL = True 380 JOIN_HINTS = False 381 TABLE_HINTS = False 382 QUERY_HINTS = False 383 SUPPORTS_TABLE_COPY = False 384 COLLATE_IS_FUNC = True 385 LIMIT_ONLY_LITERALS = True 386 JSON_KEY_VALUE_PAIR_SEP = "," 387 INSERT_OVERWRITE = " OVERWRITE INTO" 388 STRUCT_DELIMITER = ("(", ")") 389 COPY_PARAMS_ARE_WRAPPED = False 390 COPY_PARAMS_EQ_REQUIRED = True 391 STAR_EXCEPT = "EXCLUDE" 392 SUPPORTS_EXPLODING_PROJECTIONS = False 393 ARRAY_CONCAT_IS_VAR_LEN = False 394 SUPPORTS_CONVERT_TIMEZONE = True 395 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 396 SUPPORTS_MEDIAN = True 397 ARRAY_SIZE_NAME = "ARRAY_SIZE" 398 SUPPORTS_DECODE_CASE = True 399 400 AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS 401 402 IS_BOOL_ALLOWED = False 403 DIRECTED_JOINS = True 404 SUPPORTS_UESCAPE = False 405 TRY_SUPPORTED = False 406 407 TRANSFORMS = { 408 **generator.Generator.TRANSFORMS, 409 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 410 exp.ArgMax: rename_func("MAX_BY"), 411 exp.ArgMin: rename_func("MIN_BY"), 412 exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]), 413 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 414 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 415 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"), 416 exp.ArrayContains: lambda self, e: self.func( 417 "ARRAY_CONTAINS", 418 e.expression 419 if e.args.get("ensure_variant") is False 420 else exp.cast(e.expression, exp.DType.VARIANT, copy=False), 421 e.this, 422 ), 423 exp.ArrayPosition: lambda self, e: self.func( 424 "ARRAY_POSITION", 425 e.expression, 426 e.this, 427 ), 428 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 429 exp.ArrayOverlaps: rename_func("ARRAYS_OVERLAP"), 430 exp.AtTimeZone: lambda self, e: self.func("CONVERT_TIMEZONE", e.args.get("zone"), e.this), 431 exp.BitwiseOr: rename_func("BITOR"), 432 exp.BitwiseXor: rename_func("BITXOR"), 433 exp.BitwiseAnd: rename_func("BITAND"), 434 exp.BitwiseAndAgg: rename_func("BITANDAGG"), 435 exp.BitwiseOrAgg: rename_func("BITORAGG"), 436 exp.BitwiseXorAgg: rename_func("BITXORAGG"), 437 exp.BitwiseNot: rename_func("BITNOT"), 438 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 439 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 440 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 441 exp.CurrentTimestamp: lambda self, e: ( 442 self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e) 443 ), 444 exp.CurrentSchemas: lambda self, e: self.func("CURRENT_SCHEMAS"), 445 exp.Localtime: lambda self, e: ( 446 self.func("CURRENT_TIME", e.this) if e.this else "CURRENT_TIME" 447 ), 448 exp.Localtimestamp: lambda self, e: ( 449 self.func("CURRENT_TIMESTAMP", e.this) if e.this else "CURRENT_TIMESTAMP" 450 ), 451 exp.DateAdd: date_delta_sql("DATEADD"), 452 exp.DateDiff: date_delta_sql("DATEDIFF"), 453 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 454 exp.DatetimeDiff: timestampdiff_sql, 455 exp.DateStrToDate: datestrtodate_sql, 456 exp.Decrypt: lambda self, e: self.func( 457 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT", 458 e.this, 459 e.args.get("passphrase"), 460 e.args.get("aad"), 461 e.args.get("encryption_method"), 462 ), 463 exp.DecryptRaw: lambda self, e: self.func( 464 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW", 465 e.this, 466 e.args.get("key"), 467 e.args.get("iv"), 468 e.args.get("aad"), 469 e.args.get("encryption_method"), 470 e.args.get("aead"), 471 ), 472 exp.DayOfMonth: rename_func("DAYOFMONTH"), 473 exp.DayOfWeek: rename_func("DAYOFWEEK"), 474 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 475 exp.DayOfYear: rename_func("DAYOFYEAR"), 476 exp.DotProduct: rename_func("VECTOR_INNER_PRODUCT"), 477 exp.Explode: rename_func("FLATTEN"), 478 exp.Extract: lambda self, e: self.func( 479 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 480 ), 481 exp.CosineDistance: rename_func("VECTOR_COSINE_SIMILARITY"), 482 exp.EuclideanDistance: rename_func("VECTOR_L2_DISTANCE"), 483 exp.HandlerProperty: lambda self, e: f"HANDLER = {self.sql(e, 'this')}", 484 exp.FileFormatProperty: lambda self, e: ( 485 f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})" 486 ), 487 exp.FromTimeZone: lambda self, e: self.func( 488 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 489 ), 490 exp.GenerateSeries: lambda self, e: self.func( 491 "ARRAY_GENERATE_RANGE", 492 e.args["start"], 493 e.args["end"] if e.args.get("is_end_exclusive") else e.args["end"] + 1, 494 e.args.get("step"), 495 ), 496 exp.GetExtract: rename_func("GET"), 497 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 498 exp.If: if_sql(name="IFF", false_value="NULL"), 499 exp.JSONArray: lambda self, e: self.func( 500 "TO_VARIANT", self.func("ARRAY_CONSTRUCT", *e.expressions) 501 ), 502 exp.JSONExtractArray: _json_extract_value_array_sql, 503 exp.JSONExtractScalar: lambda self, e: self.func( 504 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 505 ), 506 exp.JSONKeys: rename_func("OBJECT_KEYS"), 507 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 508 exp.JSONPathRoot: lambda *_: "", 509 exp.JSONValueArray: _json_extract_value_array_sql, 510 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 511 rename_func("EDITDISTANCE") 512 ), 513 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 514 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 515 exp.LogicalOr: rename_func("BOOLOR_AGG"), 516 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 517 exp.ManhattanDistance: rename_func("VECTOR_L1_DISTANCE"), 518 exp.MakeInterval: no_make_interval_sql, 519 exp.Max: max_or_greatest, 520 exp.Min: min_or_least, 521 exp.ParseJSON: lambda self, e: self.func( 522 f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this 523 ), 524 exp.ToBinary: lambda self, e: self.func( 525 f"{'TRY_' if e.args.get('safe') else ''}TO_BINARY", e.this, e.args.get("format") 526 ), 527 exp.ToBoolean: lambda self, e: self.func( 528 f"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN", e.this 529 ), 530 exp.ToDouble: lambda self, e: self.func( 531 f"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE", e.this, e.args.get("format") 532 ), 533 exp.ToFile: lambda self, e: self.func( 534 f"{'TRY_' if e.args.get('safe') else ''}TO_FILE", e.this, e.args.get("path") 535 ), 536 exp.JSONFormat: rename_func("TO_JSON"), 537 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 538 exp.PercentileCont: transforms.preprocess([transforms.add_within_group_for_percentiles]), 539 exp.PercentileDisc: transforms.preprocess([transforms.add_within_group_for_percentiles]), 540 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 541 exp.RegexpExtract: _regexpextract_sql, 542 exp.RegexpExtractAll: _regexpextract_sql, 543 exp.RegexpILike: _regexpilike_sql, 544 exp.RowAccessProperty: lambda self, e: self.rowaccessproperty_sql(e), 545 exp.Select: transforms.preprocess( 546 [ 547 transforms.eliminate_window_clause, 548 transforms.eliminate_distinct_on, 549 transforms.explode_projection_to_unnest(), 550 transforms.eliminate_semi_and_anti_joins, 551 _transform_generate_date_array, 552 _qualify_unnested_columns, 553 _eliminate_dot_variant_lookup, 554 ] 555 ), 556 exp.SHA: rename_func("SHA1"), 557 exp.SHA1Digest: rename_func("SHA1_BINARY"), 558 exp.MD5Digest: rename_func("MD5_BINARY"), 559 exp.MD5NumberLower64: rename_func("MD5_NUMBER_LOWER64"), 560 exp.MD5NumberUpper64: rename_func("MD5_NUMBER_UPPER64"), 561 exp.Hex: rename_func("HEX_ENCODE"), 562 exp.LowerHex: rename_func("TO_CHAR"), 563 exp.Skewness: rename_func("SKEW"), 564 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 565 exp.StartsWith: rename_func("STARTSWITH"), 566 exp.EndsWith: rename_func("ENDSWITH"), 567 exp.Rand: lambda self, e: self.func("RANDOM", e.this), 568 exp.StrPosition: lambda self, e: strposition_sql( 569 self, e, func_name="CHARINDEX", supports_position=True 570 ), 571 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 572 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 573 exp.StrtokToArray: rename_func("STRTOK_TO_ARRAY"), 574 exp.Stuff: rename_func("INSERT"), 575 exp.StPoint: rename_func("ST_MAKEPOINT"), 576 exp.TimeAdd: date_delta_sql("TIMEADD"), 577 exp.TimeSlice: lambda self, e: self.func( 578 "TIME_SLICE", 579 e.this, 580 e.expression, 581 unit_to_str(e), 582 e.args.get("kind"), 583 ), 584 exp.Timestamp: no_timestamp_sql, 585 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 586 exp.TimestampDiff: lambda self, e: self.func("TIMESTAMPDIFF", e.unit, e.expression, e.this), 587 exp.TimestampTrunc: timestamptrunc_sql(), 588 exp.TimeStrToTime: timestrtotime_sql, 589 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 590 exp.ToArray: rename_func("TO_ARRAY"), 591 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 592 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 593 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 594 exp.TsOrDsToDate: lambda self, e: self.func( 595 f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e) 596 ), 597 exp.TsOrDsToTime: lambda self, e: self.func( 598 f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e) 599 ), 600 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 601 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, e.args.get("scale")), 602 exp.Uuid: rename_func("UUID_STRING"), 603 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 604 exp.Booland: rename_func("BOOLAND"), 605 exp.Boolor: rename_func("BOOLOR"), 606 exp.WeekOfYear: rename_func("WEEKISO"), 607 exp.YearOfWeek: rename_func("YEAROFWEEK"), 608 exp.YearOfWeekIso: rename_func("YEAROFWEEKISO"), 609 exp.Xor: rename_func("BOOLXOR"), 610 exp.ByteLength: rename_func("OCTET_LENGTH"), 611 exp.Flatten: rename_func("ARRAY_FLATTEN"), 612 exp.ArrayConcatAgg: lambda self, e: self.func("ARRAY_FLATTEN", exp.ArrayAgg(this=e.this)), 613 exp.SHA2Digest: lambda self, e: self.func( 614 "SHA2_BINARY", e.this, e.args.get("length") or exp.Literal.number(256) 615 ), 616 } 617 618 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 619 this = self.func("IDENTIFIER", expression.this) 620 if "expressions" in expression.args: 621 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 622 return self.func(this, *expression.expressions, normalize=False) 623 return this 624 625 def sortarray_sql(self, expression: exp.SortArray) -> str: 626 asc = expression.args.get("asc") 627 nulls_first = expression.args.get("nulls_first") 628 if asc == exp.false() and nulls_first == exp.true(): 629 nulls_first = None 630 return self.func("ARRAY_SORT", expression.this, asc, nulls_first) 631 632 def nthvalue_sql(self, expression: exp.NthValue) -> str: 633 result = self.func("NTH_VALUE", expression.this, expression.args.get("offset")) 634 635 from_first = expression.args.get("from_first") 636 637 if from_first is not None: 638 if from_first: 639 result = result + " FROM FIRST" 640 else: 641 result = result + " FROM LAST" 642 643 return result 644 645 SUPPORTED_JSON_PATH_PARTS = { 646 exp.JSONPathKey, 647 exp.JSONPathRoot, 648 exp.JSONPathSubscript, 649 } 650 651 TYPE_MAPPING = { 652 **generator.Generator.TYPE_MAPPING, 653 exp.DType.BIGDECIMAL: "DOUBLE", 654 exp.DType.JSON: "VARIANT", 655 exp.DType.NESTED: "OBJECT", 656 exp.DType.STRUCT: "OBJECT", 657 exp.DType.TEXT: "VARCHAR", 658 } 659 660 TOKEN_MAPPING = { 661 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 662 } 663 664 PROPERTIES_LOCATION = { 665 **generator.Generator.PROPERTIES_LOCATION, 666 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 667 exp.LocationProperty: exp.Properties.Location.POST_WITH, 668 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.RowAccessProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 671 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 672 } 673 674 UNSUPPORTED_VALUES_EXPRESSIONS = { 675 exp.Map, 676 exp.StarMap, 677 exp.Struct, 678 exp.VarMap, 679 } 680 681 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 682 683 def with_properties(self, properties: exp.Properties) -> str: 684 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 685 686 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 687 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 688 values_as_table = False 689 690 return super().values_sql(expression, values_as_table=values_as_table) 691 692 def datatype_sql(self, expression: exp.DataType) -> str: 693 # Check if this is a FLOAT type nested inside a VECTOR type 694 # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types 695 # https://docs.snowflake.com/en/sql-reference/data-types-vector 696 if expression.is_type(exp.DType.DOUBLE): 697 parent = expression.parent 698 if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR): 699 # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE 700 return "FLOAT" 701 702 expressions = expression.expressions 703 if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES): 704 for field_type in expressions: 705 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 706 if isinstance(field_type, exp.DataType): 707 return "OBJECT" 708 if ( 709 isinstance(field_type, exp.ColumnDef) 710 and field_type.this 711 and field_type.this.is_string 712 ): 713 # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides 714 # converting 'foo' into an identifier, we also need to quote it because these 715 # keys are case-sensitive. For example: 716 # 717 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct 718 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL 719 field_type.this.replace(exp.to_identifier(field_type.name, quoted=True)) 720 721 return super().datatype_sql(expression) 722 723 def tonumber_sql(self, expression: exp.ToNumber) -> str: 724 precision = expression.args.get("precision") 725 scale = expression.args.get("scale") 726 727 default_precision = isinstance(precision, exp.Literal) and precision.name == "38" 728 default_scale = isinstance(scale, exp.Literal) and scale.name == "0" 729 730 if default_precision and default_scale: 731 precision = None 732 scale = None 733 elif default_scale: 734 scale = None 735 736 func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER" 737 738 return self.func( 739 func_name, 740 expression.this, 741 expression.args.get("format"), 742 precision, 743 scale, 744 ) 745 746 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 747 milli = expression.args.get("milli") 748 if milli is not None: 749 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 750 expression.set("nano", milli_to_nano) 751 752 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 753 754 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 755 if expression.is_type(exp.DType.GEOGRAPHY): 756 return self.func("TO_GEOGRAPHY", expression.this) 757 if expression.is_type(exp.DType.GEOMETRY): 758 return self.func("TO_GEOMETRY", expression.this) 759 760 return super().cast_sql(expression, safe_prefix=safe_prefix) 761 762 def trycast_sql(self, expression: exp.TryCast) -> str: 763 value = expression.this 764 765 if value.type is None: 766 from sqlglot.optimizer.annotate_types import annotate_types 767 768 value = annotate_types(value, dialect=self.dialect) 769 770 # Snowflake requires that TRY_CAST's value be a string 771 # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or 772 # if we can deduce that the value is a string, then we can generate TRY_CAST 773 if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES): 774 return super().trycast_sql(expression) 775 776 return self.cast_sql(expression) 777 778 def log_sql(self, expression: exp.Log) -> str: 779 if not expression.expression: 780 return self.func("LN", expression.this) 781 782 return super().log_sql(expression) 783 784 def greatest_sql(self, expression: exp.Greatest) -> str: 785 name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST" 786 return self.func(name, expression.this, *expression.expressions) 787 788 def least_sql(self, expression: exp.Least) -> str: 789 name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST" 790 return self.func(name, expression.this, *expression.expressions) 791 792 def generator_sql(self, expression: exp.Generator) -> str: 793 args = [] 794 rowcount = expression.args.get("rowcount") 795 timelimit = expression.args.get("timelimit") 796 797 if rowcount: 798 args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount)) 799 if timelimit: 800 args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit)) 801 802 return self.func("GENERATOR", *args) 803 804 def unnest_sql(self, expression: exp.Unnest) -> str: 805 unnest_alias = expression.args.get("alias") 806 offset = expression.args.get("offset") 807 808 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 809 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 810 811 columns = [ 812 exp.to_identifier("seq"), 813 exp.to_identifier("key"), 814 exp.to_identifier("path"), 815 offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"), 816 value, 817 exp.to_identifier("this"), 818 ] 819 820 if unnest_alias: 821 unnest_alias.set("columns", columns) 822 else: 823 unnest_alias = exp.TableAlias(this="_u", columns=columns) 824 825 table_input = self.sql(expression.expressions[0]) 826 if not table_input.startswith("INPUT =>"): 827 table_input = f"INPUT => {table_input}" 828 829 expression_parent = expression.parent 830 831 explode = ( 832 f"FLATTEN({table_input})" 833 if isinstance(expression_parent, exp.Lateral) 834 else f"TABLE(FLATTEN({table_input}))" 835 ) 836 alias = self.sql(unnest_alias) 837 alias = f" AS {alias}" if alias else "" 838 value = ( 839 "" 840 if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral)) 841 else f"{value} FROM " 842 ) 843 844 return f"{value}{explode}{alias}" 845 846 def undrop_sql(self, expression: exp.Undrop) -> str: 847 this = self.sql(expression, "this") 848 kind = expression.kind 849 rename = self.sql(expression, "rename") 850 rename = f" RENAME TO {rename}" if rename else "" 851 return f"UNDROP {kind} {this}{rename}" 852 853 def show_sql(self, expression: exp.Show) -> str: 854 terse = "TERSE " if expression.args.get("terse") else "" 855 iceberg = "ICEBERG " if expression.args.get("iceberg") else "" 856 history = " HISTORY" if expression.args.get("history") else "" 857 like = self.sql(expression, "like") 858 like = f" LIKE {like}" if like else "" 859 860 scope = self.sql(expression, "scope") 861 scope = f" {scope}" if scope else "" 862 863 scope_kind = self.sql(expression, "scope_kind") 864 if scope_kind: 865 scope_kind = f" IN {scope_kind}" 866 867 starts_with = self.sql(expression, "starts_with") 868 if starts_with: 869 starts_with = f" STARTS WITH {starts_with}" 870 871 limit = self.sql(expression, "limit") 872 873 from_ = self.sql(expression, "from_") 874 if from_: 875 from_ = f" FROM {from_}" 876 877 privileges = self.expressions(expression, key="privileges", flat=True) 878 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 879 880 return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 881 882 def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str: 883 if not expression.this: 884 return "ROW ACCESS" 885 on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else "" 886 return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}" 887 888 def describe_sql(self, expression: exp.Describe) -> str: 889 kind_value = expression.args.get("kind") or "TABLE" 890 891 properties = expression.args.get("properties") 892 if properties: 893 qualifier = self.expressions(properties, sep=" ") 894 kind = f" {qualifier} {kind_value}" 895 else: 896 kind = f" {kind_value}" 897 898 this = f" {self.sql(expression, 'this')}" 899 expressions = self.expressions(expression, flat=True) 900 expressions = f" {expressions}" if expressions else "" 901 return f"DESCRIBE{kind}{this}{expressions}" 902 903 def generatedasidentitycolumnconstraint_sql( 904 self, expression: exp.GeneratedAsIdentityColumnConstraint 905 ) -> str: 906 start = expression.args.get("start") 907 start = f" START {start}" if start else "" 908 increment = expression.args.get("increment") 909 increment = f" INCREMENT {increment}" if increment else "" 910 911 order = expression.args.get("order") 912 if order is not None: 913 order_clause = " ORDER" if order else " NOORDER" 914 else: 915 order_clause = "" 916 917 return f"AUTOINCREMENT{start}{increment}{order_clause}" 918 919 def struct_sql(self, expression: exp.Struct) -> str: 920 if len(expression.expressions) == 1: 921 arg = expression.expressions[0] 922 if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star): 923 # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object 924 return f"{{{self.sql(expression.expressions[0])}}}" 925 926 keys = [] 927 values = [] 928 929 for i, e in enumerate(expression.expressions): 930 if isinstance(e, exp.PropertyEQ): 931 keys.append( 932 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 933 ) 934 values.append(e.expression) 935 else: 936 keys.append(exp.Literal.string(f"_{i}")) 937 values.append(e) 938 939 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 940 941 @unsupported_args("weight", "accuracy") 942 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 943 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 944 945 def alterset_sql(self, expression: exp.AlterSet) -> str: 946 exprs = self.expressions(expression, flat=True) 947 exprs = f" {exprs}" if exprs else "" 948 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 949 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 950 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 951 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 952 tag = self.expressions(expression, key="tag", flat=True) 953 tag = f" TAG {tag}" if tag else "" 954 955 return f"SET{exprs}{file_format}{copy_options}{tag}" 956 957 def strtotime_sql(self, expression: exp.StrToTime): 958 # target_type is stored as a DataType instance 959 target_type = expression.args.get("target_type") 960 961 # Get the type enum from DataType instance or from type annotation 962 if isinstance(target_type, exp.DataType): 963 type_enum = target_type.this 964 elif expression.type: 965 type_enum = expression.type.this 966 else: 967 type_enum = exp.DType.TIMESTAMP 968 969 func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP") 970 971 return self.func( 972 f"{'TRY_' if expression.args.get('safe') else ''}{func_name}", 973 expression.this, 974 self.format_time(expression), 975 ) 976 977 def timestampsub_sql(self, expression: exp.TimestampSub): 978 return self.sql( 979 exp.TimestampAdd( 980 this=expression.this, 981 expression=expression.expression * -1, 982 unit=expression.unit, 983 ) 984 ) 985 986 def jsonextract_sql(self, expression: exp.JSONExtract): 987 this = expression.this 988 989 # JSON strings are valid coming from other dialects such as BQ so 990 # for these cases we PARSE_JSON preemptively 991 if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get( 992 "requires_json" 993 ): 994 this = exp.ParseJSON(this=this) 995 996 return self.func( 997 "GET_PATH", 998 this, 999 expression.expression, 1000 ) 1001 1002 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 1003 this = expression.this 1004 if this.is_string: 1005 this = exp.cast(this, exp.DType.TIMESTAMP) 1006 1007 return self.func("TO_CHAR", this, self.format_time(expression)) 1008 1009 def datesub_sql(self, expression: exp.DateSub) -> str: 1010 value = expression.expression 1011 if value: 1012 value.replace(value * (-1)) 1013 else: 1014 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1015 1016 return date_delta_sql("DATEADD")(self, expression) 1017 1018 def select_sql(self, expression: exp.Select) -> str: 1019 limit = expression.args.get("limit") 1020 offset = expression.args.get("offset") 1021 if offset and not limit: 1022 expression.limit(exp.Null(), copy=False) 1023 return super().select_sql(expression) 1024 1025 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1026 is_materialized = expression.find(exp.MaterializedProperty) 1027 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1028 1029 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1030 # For materialized views, COPY GRANTS is located *before* the columns list 1031 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1032 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1033 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1034 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1035 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1036 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1037 1038 this_name = self.sql(expression.this, "this") 1039 copy_grants = self.sql(copy_grants_property) 1040 this_schema = self.schema_columns_sql(expression.this) 1041 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1042 1043 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1044 1045 return super().createable_sql(expression, locations) 1046 1047 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1048 this = expression.this 1049 1050 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1051 # and add it later as part of the WITHIN GROUP clause 1052 order = this if isinstance(this, exp.Order) else None 1053 if order: 1054 expression.set("this", order.this.pop()) 1055 1056 expr_sql = super().arrayagg_sql(expression) 1057 1058 if order: 1059 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1060 1061 return expr_sql 1062 1063 def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str: 1064 if expression.args.get("check_null"): 1065 return self.func("ARRAY_DISTINCT", expression.this) 1066 return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this)) 1067 1068 def arraytostring_sql(self, expression: exp.ArrayToString) -> str: 1069 return self.func("ARRAY_TO_STRING", expression.this, expression.expression) 1070 1071 def array_sql(self, expression: exp.Array) -> str: 1072 expressions = expression.expressions 1073 1074 first_expr = seq_get(expressions, 0) 1075 if isinstance(first_expr, exp.Select): 1076 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1077 if first_expr.text("kind").upper() == "STRUCT": 1078 object_construct_args = [] 1079 for expr in first_expr.expressions: 1080 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1081 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1082 name = expr.this if isinstance(expr, exp.Alias) else expr 1083 1084 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1085 1086 array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args)) 1087 1088 first_expr.set("kind", None) 1089 first_expr.set("expressions", [array_agg]) 1090 1091 return self.sql(first_expr.subquery()) 1092 1093 return inline_array_sql(self, expression) 1094 1095 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1096 zone = self.sql(expression, "this") 1097 if not zone: 1098 return super().currentdate_sql(expression) 1099 1100 expr = exp.Cast( 1101 this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()), 1102 to=exp.DataType(this=exp.DType.DATE), 1103 ) 1104 return self.sql(expr) 1105 1106 def dot_sql(self, expression: exp.Dot) -> str: 1107 this = expression.this 1108 1109 if not this.type: 1110 from sqlglot.optimizer.annotate_types import annotate_types 1111 1112 this = annotate_types(this, dialect=self.dialect) 1113 1114 if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT): 1115 # Generate colon notation for the top level STRUCT 1116 return f"{self.sql(this)}:{self.sql(expression, 'expression')}" 1117 1118 return super().dot_sql(expression) 1119 1120 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 1121 return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}" 1122 1123 def format_sql(self, expression: exp.Format) -> str: 1124 if expression.name.lower() == "%s" and len(expression.expressions) == 1: 1125 return self.func("TO_CHAR", expression.expressions[0]) 1126 1127 return self.function_fallback_sql(expression) 1128 1129 def splitpart_sql(self, expression: exp.SplitPart) -> str: 1130 # Set part_index to 1 if missing 1131 if not expression.args.get("delimiter"): 1132 expression.set("delimiter", exp.Literal.string(" ")) 1133 1134 if not expression.args.get("part_index"): 1135 expression.set("part_index", exp.Literal.number(1)) 1136 1137 return rename_func("SPLIT_PART")(self, expression) 1138 1139 def uniform_sql(self, expression: exp.Uniform) -> str: 1140 gen = expression.args.get("gen") 1141 seed = expression.args.get("seed") 1142 1143 # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed) 1144 if seed: 1145 gen = exp.Rand(this=seed) 1146 1147 # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM() 1148 if not gen: 1149 gen = exp.Rand() 1150 1151 return self.func("UNIFORM", expression.this, expression.expression, gen) 1152 1153 def window_sql(self, expression: exp.Window) -> str: 1154 spec = expression.args.get("spec") 1155 this = expression.this 1156 1157 if ( 1158 ( 1159 isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1160 or ( 1161 isinstance(this, (exp.RespectNulls, exp.IgnoreNulls)) 1162 and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1163 ) 1164 ) 1165 and spec 1166 and ( 1167 spec.text("kind").upper() == "ROWS" 1168 and spec.text("start").upper() == "UNBOUNDED" 1169 and spec.text("start_side").upper() == "PRECEDING" 1170 and spec.text("end").upper() == "UNBOUNDED" 1171 and spec.text("end_side").upper() == "FOLLOWING" 1172 ) 1173 ): 1174 # omit the default window from window ranking functions 1175 expression.set("spec", None) 1176 return super().window_sql(expression) 1177 1178 def filter_sql(self, expression: exp.Filter) -> str: 1179 # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an 1180 # equivalent conditional aggregation, i.e. wrap the input values in an IFF 1181 agg = expression.this 1182 agg_arg = agg.this 1183 cond = expression.expression.this 1184 1185 if isinstance(agg, exp.WithinGroup): 1186 # Ordered-set aggregates take their input from the ORDER BY key, so the 1187 # condition has to wrap that instead of the aggregate's own argument 1188 if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)): 1189 for ordered in agg.expression.expressions: 1190 key = ordered.this 1191 key.replace(exp.If(this=cond.copy(), true=key.copy())) 1192 1193 return self.sql(agg) 1194 1195 # Besides the percentile functions, these are the only functions Snowflake 1196 # accepts WITHIN GROUP for, so anything else can't be rewritten correctly 1197 if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)): 1198 agg_arg = agg_arg.this 1199 else: 1200 self.unsupported("Unable to rewrite FILTER into the aggregate's arguments") 1201 return self.sql(agg) 1202 1203 # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF 1204 # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of 1205 # them, which Snowflake rejects. Use its native COUNT_IF instead. 1206 if isinstance(agg, exp.Count) and agg_arg.is_star: 1207 return self.func("COUNT_IF", cond) 1208 1209 # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the 1210 # condition has to wrap the values underneath them rather than the whole clause -- 1211 # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts. 1212 if isinstance(agg_arg, exp.Order): 1213 agg_arg = agg_arg.this 1214 1215 if isinstance(agg_arg, exp.Distinct): 1216 targets = agg_arg.expressions 1217 else: 1218 targets = [agg_arg] 1219 1220 for target in targets: 1221 target.replace(exp.If(this=cond.copy(), true=target.copy())) 1222 1223 return self.sql(agg) 1224 1225 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1226 # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only 1227 # accepts the value to aggregate as an argument: MODE(<expr>) 1228 if isinstance(expression.this, exp.Mode) and not expression.this.this: 1229 order = expression.expression 1230 if isinstance(order, exp.Order) and len(order.expressions) == 1: 1231 return self.sql(exp.Mode(this=order.expressions[0].this)) 1232 1233 return super().withingroup_sql(expression)
376class SnowflakeGenerator(generator.Generator): 377 SELECT_KINDS: tuple[str, ...] = () 378 PARAMETER_TOKEN = "$" 379 MATCHED_BY_SOURCE = False 380 SINGLE_STRING_INTERVAL = True 381 JOIN_HINTS = False 382 TABLE_HINTS = False 383 QUERY_HINTS = False 384 SUPPORTS_TABLE_COPY = False 385 COLLATE_IS_FUNC = True 386 LIMIT_ONLY_LITERALS = True 387 JSON_KEY_VALUE_PAIR_SEP = "," 388 INSERT_OVERWRITE = " OVERWRITE INTO" 389 STRUCT_DELIMITER = ("(", ")") 390 COPY_PARAMS_ARE_WRAPPED = False 391 COPY_PARAMS_EQ_REQUIRED = True 392 STAR_EXCEPT = "EXCLUDE" 393 SUPPORTS_EXPLODING_PROJECTIONS = False 394 ARRAY_CONCAT_IS_VAR_LEN = False 395 SUPPORTS_CONVERT_TIMEZONE = True 396 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 397 SUPPORTS_MEDIAN = True 398 ARRAY_SIZE_NAME = "ARRAY_SIZE" 399 SUPPORTS_DECODE_CASE = True 400 401 AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS 402 403 IS_BOOL_ALLOWED = False 404 DIRECTED_JOINS = True 405 SUPPORTS_UESCAPE = False 406 TRY_SUPPORTED = False 407 408 TRANSFORMS = { 409 **generator.Generator.TRANSFORMS, 410 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 411 exp.ArgMax: rename_func("MAX_BY"), 412 exp.ArgMin: rename_func("MIN_BY"), 413 exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]), 414 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 415 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 416 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"), 417 exp.ArrayContains: lambda self, e: self.func( 418 "ARRAY_CONTAINS", 419 e.expression 420 if e.args.get("ensure_variant") is False 421 else exp.cast(e.expression, exp.DType.VARIANT, copy=False), 422 e.this, 423 ), 424 exp.ArrayPosition: lambda self, e: self.func( 425 "ARRAY_POSITION", 426 e.expression, 427 e.this, 428 ), 429 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 430 exp.ArrayOverlaps: rename_func("ARRAYS_OVERLAP"), 431 exp.AtTimeZone: lambda self, e: self.func("CONVERT_TIMEZONE", e.args.get("zone"), e.this), 432 exp.BitwiseOr: rename_func("BITOR"), 433 exp.BitwiseXor: rename_func("BITXOR"), 434 exp.BitwiseAnd: rename_func("BITAND"), 435 exp.BitwiseAndAgg: rename_func("BITANDAGG"), 436 exp.BitwiseOrAgg: rename_func("BITORAGG"), 437 exp.BitwiseXorAgg: rename_func("BITXORAGG"), 438 exp.BitwiseNot: rename_func("BITNOT"), 439 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 440 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 441 exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]), 442 exp.CurrentTimestamp: lambda self, e: ( 443 self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e) 444 ), 445 exp.CurrentSchemas: lambda self, e: self.func("CURRENT_SCHEMAS"), 446 exp.Localtime: lambda self, e: ( 447 self.func("CURRENT_TIME", e.this) if e.this else "CURRENT_TIME" 448 ), 449 exp.Localtimestamp: lambda self, e: ( 450 self.func("CURRENT_TIMESTAMP", e.this) if e.this else "CURRENT_TIMESTAMP" 451 ), 452 exp.DateAdd: date_delta_sql("DATEADD"), 453 exp.DateDiff: date_delta_sql("DATEDIFF"), 454 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 455 exp.DatetimeDiff: timestampdiff_sql, 456 exp.DateStrToDate: datestrtodate_sql, 457 exp.Decrypt: lambda self, e: self.func( 458 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT", 459 e.this, 460 e.args.get("passphrase"), 461 e.args.get("aad"), 462 e.args.get("encryption_method"), 463 ), 464 exp.DecryptRaw: lambda self, e: self.func( 465 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW", 466 e.this, 467 e.args.get("key"), 468 e.args.get("iv"), 469 e.args.get("aad"), 470 e.args.get("encryption_method"), 471 e.args.get("aead"), 472 ), 473 exp.DayOfMonth: rename_func("DAYOFMONTH"), 474 exp.DayOfWeek: rename_func("DAYOFWEEK"), 475 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 476 exp.DayOfYear: rename_func("DAYOFYEAR"), 477 exp.DotProduct: rename_func("VECTOR_INNER_PRODUCT"), 478 exp.Explode: rename_func("FLATTEN"), 479 exp.Extract: lambda self, e: self.func( 480 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 481 ), 482 exp.CosineDistance: rename_func("VECTOR_COSINE_SIMILARITY"), 483 exp.EuclideanDistance: rename_func("VECTOR_L2_DISTANCE"), 484 exp.HandlerProperty: lambda self, e: f"HANDLER = {self.sql(e, 'this')}", 485 exp.FileFormatProperty: lambda self, e: ( 486 f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})" 487 ), 488 exp.FromTimeZone: lambda self, e: self.func( 489 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 490 ), 491 exp.GenerateSeries: lambda self, e: self.func( 492 "ARRAY_GENERATE_RANGE", 493 e.args["start"], 494 e.args["end"] if e.args.get("is_end_exclusive") else e.args["end"] + 1, 495 e.args.get("step"), 496 ), 497 exp.GetExtract: rename_func("GET"), 498 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 499 exp.If: if_sql(name="IFF", false_value="NULL"), 500 exp.JSONArray: lambda self, e: self.func( 501 "TO_VARIANT", self.func("ARRAY_CONSTRUCT", *e.expressions) 502 ), 503 exp.JSONExtractArray: _json_extract_value_array_sql, 504 exp.JSONExtractScalar: lambda self, e: self.func( 505 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 506 ), 507 exp.JSONKeys: rename_func("OBJECT_KEYS"), 508 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 509 exp.JSONPathRoot: lambda *_: "", 510 exp.JSONValueArray: _json_extract_value_array_sql, 511 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 512 rename_func("EDITDISTANCE") 513 ), 514 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 515 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 516 exp.LogicalOr: rename_func("BOOLOR_AGG"), 517 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 518 exp.ManhattanDistance: rename_func("VECTOR_L1_DISTANCE"), 519 exp.MakeInterval: no_make_interval_sql, 520 exp.Max: max_or_greatest, 521 exp.Min: min_or_least, 522 exp.ParseJSON: lambda self, e: self.func( 523 f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this 524 ), 525 exp.ToBinary: lambda self, e: self.func( 526 f"{'TRY_' if e.args.get('safe') else ''}TO_BINARY", e.this, e.args.get("format") 527 ), 528 exp.ToBoolean: lambda self, e: self.func( 529 f"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN", e.this 530 ), 531 exp.ToDouble: lambda self, e: self.func( 532 f"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE", e.this, e.args.get("format") 533 ), 534 exp.ToFile: lambda self, e: self.func( 535 f"{'TRY_' if e.args.get('safe') else ''}TO_FILE", e.this, e.args.get("path") 536 ), 537 exp.JSONFormat: rename_func("TO_JSON"), 538 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 539 exp.PercentileCont: transforms.preprocess([transforms.add_within_group_for_percentiles]), 540 exp.PercentileDisc: transforms.preprocess([transforms.add_within_group_for_percentiles]), 541 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 542 exp.RegexpExtract: _regexpextract_sql, 543 exp.RegexpExtractAll: _regexpextract_sql, 544 exp.RegexpILike: _regexpilike_sql, 545 exp.RowAccessProperty: lambda self, e: self.rowaccessproperty_sql(e), 546 exp.Select: transforms.preprocess( 547 [ 548 transforms.eliminate_window_clause, 549 transforms.eliminate_distinct_on, 550 transforms.explode_projection_to_unnest(), 551 transforms.eliminate_semi_and_anti_joins, 552 _transform_generate_date_array, 553 _qualify_unnested_columns, 554 _eliminate_dot_variant_lookup, 555 ] 556 ), 557 exp.SHA: rename_func("SHA1"), 558 exp.SHA1Digest: rename_func("SHA1_BINARY"), 559 exp.MD5Digest: rename_func("MD5_BINARY"), 560 exp.MD5NumberLower64: rename_func("MD5_NUMBER_LOWER64"), 561 exp.MD5NumberUpper64: rename_func("MD5_NUMBER_UPPER64"), 562 exp.Hex: rename_func("HEX_ENCODE"), 563 exp.LowerHex: rename_func("TO_CHAR"), 564 exp.Skewness: rename_func("SKEW"), 565 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 566 exp.StartsWith: rename_func("STARTSWITH"), 567 exp.EndsWith: rename_func("ENDSWITH"), 568 exp.Rand: lambda self, e: self.func("RANDOM", e.this), 569 exp.StrPosition: lambda self, e: strposition_sql( 570 self, e, func_name="CHARINDEX", supports_position=True 571 ), 572 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 573 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 574 exp.StrtokToArray: rename_func("STRTOK_TO_ARRAY"), 575 exp.Stuff: rename_func("INSERT"), 576 exp.StPoint: rename_func("ST_MAKEPOINT"), 577 exp.TimeAdd: date_delta_sql("TIMEADD"), 578 exp.TimeSlice: lambda self, e: self.func( 579 "TIME_SLICE", 580 e.this, 581 e.expression, 582 unit_to_str(e), 583 e.args.get("kind"), 584 ), 585 exp.Timestamp: no_timestamp_sql, 586 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 587 exp.TimestampDiff: lambda self, e: self.func("TIMESTAMPDIFF", e.unit, e.expression, e.this), 588 exp.TimestampTrunc: timestamptrunc_sql(), 589 exp.TimeStrToTime: timestrtotime_sql, 590 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 591 exp.ToArray: rename_func("TO_ARRAY"), 592 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 593 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 594 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 595 exp.TsOrDsToDate: lambda self, e: self.func( 596 f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e) 597 ), 598 exp.TsOrDsToTime: lambda self, e: self.func( 599 f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e) 600 ), 601 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 602 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, e.args.get("scale")), 603 exp.Uuid: rename_func("UUID_STRING"), 604 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 605 exp.Booland: rename_func("BOOLAND"), 606 exp.Boolor: rename_func("BOOLOR"), 607 exp.WeekOfYear: rename_func("WEEKISO"), 608 exp.YearOfWeek: rename_func("YEAROFWEEK"), 609 exp.YearOfWeekIso: rename_func("YEAROFWEEKISO"), 610 exp.Xor: rename_func("BOOLXOR"), 611 exp.ByteLength: rename_func("OCTET_LENGTH"), 612 exp.Flatten: rename_func("ARRAY_FLATTEN"), 613 exp.ArrayConcatAgg: lambda self, e: self.func("ARRAY_FLATTEN", exp.ArrayAgg(this=e.this)), 614 exp.SHA2Digest: lambda self, e: self.func( 615 "SHA2_BINARY", e.this, e.args.get("length") or exp.Literal.number(256) 616 ), 617 } 618 619 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 620 this = self.func("IDENTIFIER", expression.this) 621 if "expressions" in expression.args: 622 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 623 return self.func(this, *expression.expressions, normalize=False) 624 return this 625 626 def sortarray_sql(self, expression: exp.SortArray) -> str: 627 asc = expression.args.get("asc") 628 nulls_first = expression.args.get("nulls_first") 629 if asc == exp.false() and nulls_first == exp.true(): 630 nulls_first = None 631 return self.func("ARRAY_SORT", expression.this, asc, nulls_first) 632 633 def nthvalue_sql(self, expression: exp.NthValue) -> str: 634 result = self.func("NTH_VALUE", expression.this, expression.args.get("offset")) 635 636 from_first = expression.args.get("from_first") 637 638 if from_first is not None: 639 if from_first: 640 result = result + " FROM FIRST" 641 else: 642 result = result + " FROM LAST" 643 644 return result 645 646 SUPPORTED_JSON_PATH_PARTS = { 647 exp.JSONPathKey, 648 exp.JSONPathRoot, 649 exp.JSONPathSubscript, 650 } 651 652 TYPE_MAPPING = { 653 **generator.Generator.TYPE_MAPPING, 654 exp.DType.BIGDECIMAL: "DOUBLE", 655 exp.DType.JSON: "VARIANT", 656 exp.DType.NESTED: "OBJECT", 657 exp.DType.STRUCT: "OBJECT", 658 exp.DType.TEXT: "VARCHAR", 659 } 660 661 TOKEN_MAPPING = { 662 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 663 } 664 665 PROPERTIES_LOCATION = { 666 **generator.Generator.PROPERTIES_LOCATION, 667 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 668 exp.LocationProperty: exp.Properties.Location.POST_WITH, 669 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.RowAccessProperty: exp.Properties.Location.POST_SCHEMA, 671 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 672 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 673 } 674 675 UNSUPPORTED_VALUES_EXPRESSIONS = { 676 exp.Map, 677 exp.StarMap, 678 exp.Struct, 679 exp.VarMap, 680 } 681 682 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 683 684 def with_properties(self, properties: exp.Properties) -> str: 685 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 686 687 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 688 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 689 values_as_table = False 690 691 return super().values_sql(expression, values_as_table=values_as_table) 692 693 def datatype_sql(self, expression: exp.DataType) -> str: 694 # Check if this is a FLOAT type nested inside a VECTOR type 695 # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types 696 # https://docs.snowflake.com/en/sql-reference/data-types-vector 697 if expression.is_type(exp.DType.DOUBLE): 698 parent = expression.parent 699 if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR): 700 # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE 701 return "FLOAT" 702 703 expressions = expression.expressions 704 if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES): 705 for field_type in expressions: 706 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 707 if isinstance(field_type, exp.DataType): 708 return "OBJECT" 709 if ( 710 isinstance(field_type, exp.ColumnDef) 711 and field_type.this 712 and field_type.this.is_string 713 ): 714 # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides 715 # converting 'foo' into an identifier, we also need to quote it because these 716 # keys are case-sensitive. For example: 717 # 718 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct 719 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL 720 field_type.this.replace(exp.to_identifier(field_type.name, quoted=True)) 721 722 return super().datatype_sql(expression) 723 724 def tonumber_sql(self, expression: exp.ToNumber) -> str: 725 precision = expression.args.get("precision") 726 scale = expression.args.get("scale") 727 728 default_precision = isinstance(precision, exp.Literal) and precision.name == "38" 729 default_scale = isinstance(scale, exp.Literal) and scale.name == "0" 730 731 if default_precision and default_scale: 732 precision = None 733 scale = None 734 elif default_scale: 735 scale = None 736 737 func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER" 738 739 return self.func( 740 func_name, 741 expression.this, 742 expression.args.get("format"), 743 precision, 744 scale, 745 ) 746 747 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 748 milli = expression.args.get("milli") 749 if milli is not None: 750 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 751 expression.set("nano", milli_to_nano) 752 753 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 754 755 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 756 if expression.is_type(exp.DType.GEOGRAPHY): 757 return self.func("TO_GEOGRAPHY", expression.this) 758 if expression.is_type(exp.DType.GEOMETRY): 759 return self.func("TO_GEOMETRY", expression.this) 760 761 return super().cast_sql(expression, safe_prefix=safe_prefix) 762 763 def trycast_sql(self, expression: exp.TryCast) -> str: 764 value = expression.this 765 766 if value.type is None: 767 from sqlglot.optimizer.annotate_types import annotate_types 768 769 value = annotate_types(value, dialect=self.dialect) 770 771 # Snowflake requires that TRY_CAST's value be a string 772 # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or 773 # if we can deduce that the value is a string, then we can generate TRY_CAST 774 if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES): 775 return super().trycast_sql(expression) 776 777 return self.cast_sql(expression) 778 779 def log_sql(self, expression: exp.Log) -> str: 780 if not expression.expression: 781 return self.func("LN", expression.this) 782 783 return super().log_sql(expression) 784 785 def greatest_sql(self, expression: exp.Greatest) -> str: 786 name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST" 787 return self.func(name, expression.this, *expression.expressions) 788 789 def least_sql(self, expression: exp.Least) -> str: 790 name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST" 791 return self.func(name, expression.this, *expression.expressions) 792 793 def generator_sql(self, expression: exp.Generator) -> str: 794 args = [] 795 rowcount = expression.args.get("rowcount") 796 timelimit = expression.args.get("timelimit") 797 798 if rowcount: 799 args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount)) 800 if timelimit: 801 args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit)) 802 803 return self.func("GENERATOR", *args) 804 805 def unnest_sql(self, expression: exp.Unnest) -> str: 806 unnest_alias = expression.args.get("alias") 807 offset = expression.args.get("offset") 808 809 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 810 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 811 812 columns = [ 813 exp.to_identifier("seq"), 814 exp.to_identifier("key"), 815 exp.to_identifier("path"), 816 offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"), 817 value, 818 exp.to_identifier("this"), 819 ] 820 821 if unnest_alias: 822 unnest_alias.set("columns", columns) 823 else: 824 unnest_alias = exp.TableAlias(this="_u", columns=columns) 825 826 table_input = self.sql(expression.expressions[0]) 827 if not table_input.startswith("INPUT =>"): 828 table_input = f"INPUT => {table_input}" 829 830 expression_parent = expression.parent 831 832 explode = ( 833 f"FLATTEN({table_input})" 834 if isinstance(expression_parent, exp.Lateral) 835 else f"TABLE(FLATTEN({table_input}))" 836 ) 837 alias = self.sql(unnest_alias) 838 alias = f" AS {alias}" if alias else "" 839 value = ( 840 "" 841 if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral)) 842 else f"{value} FROM " 843 ) 844 845 return f"{value}{explode}{alias}" 846 847 def undrop_sql(self, expression: exp.Undrop) -> str: 848 this = self.sql(expression, "this") 849 kind = expression.kind 850 rename = self.sql(expression, "rename") 851 rename = f" RENAME TO {rename}" if rename else "" 852 return f"UNDROP {kind} {this}{rename}" 853 854 def show_sql(self, expression: exp.Show) -> str: 855 terse = "TERSE " if expression.args.get("terse") else "" 856 iceberg = "ICEBERG " if expression.args.get("iceberg") else "" 857 history = " HISTORY" if expression.args.get("history") else "" 858 like = self.sql(expression, "like") 859 like = f" LIKE {like}" if like else "" 860 861 scope = self.sql(expression, "scope") 862 scope = f" {scope}" if scope else "" 863 864 scope_kind = self.sql(expression, "scope_kind") 865 if scope_kind: 866 scope_kind = f" IN {scope_kind}" 867 868 starts_with = self.sql(expression, "starts_with") 869 if starts_with: 870 starts_with = f" STARTS WITH {starts_with}" 871 872 limit = self.sql(expression, "limit") 873 874 from_ = self.sql(expression, "from_") 875 if from_: 876 from_ = f" FROM {from_}" 877 878 privileges = self.expressions(expression, key="privileges", flat=True) 879 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 880 881 return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 882 883 def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str: 884 if not expression.this: 885 return "ROW ACCESS" 886 on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else "" 887 return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}" 888 889 def describe_sql(self, expression: exp.Describe) -> str: 890 kind_value = expression.args.get("kind") or "TABLE" 891 892 properties = expression.args.get("properties") 893 if properties: 894 qualifier = self.expressions(properties, sep=" ") 895 kind = f" {qualifier} {kind_value}" 896 else: 897 kind = f" {kind_value}" 898 899 this = f" {self.sql(expression, 'this')}" 900 expressions = self.expressions(expression, flat=True) 901 expressions = f" {expressions}" if expressions else "" 902 return f"DESCRIBE{kind}{this}{expressions}" 903 904 def generatedasidentitycolumnconstraint_sql( 905 self, expression: exp.GeneratedAsIdentityColumnConstraint 906 ) -> str: 907 start = expression.args.get("start") 908 start = f" START {start}" if start else "" 909 increment = expression.args.get("increment") 910 increment = f" INCREMENT {increment}" if increment else "" 911 912 order = expression.args.get("order") 913 if order is not None: 914 order_clause = " ORDER" if order else " NOORDER" 915 else: 916 order_clause = "" 917 918 return f"AUTOINCREMENT{start}{increment}{order_clause}" 919 920 def struct_sql(self, expression: exp.Struct) -> str: 921 if len(expression.expressions) == 1: 922 arg = expression.expressions[0] 923 if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star): 924 # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object 925 return f"{{{self.sql(expression.expressions[0])}}}" 926 927 keys = [] 928 values = [] 929 930 for i, e in enumerate(expression.expressions): 931 if isinstance(e, exp.PropertyEQ): 932 keys.append( 933 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 934 ) 935 values.append(e.expression) 936 else: 937 keys.append(exp.Literal.string(f"_{i}")) 938 values.append(e) 939 940 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 941 942 @unsupported_args("weight", "accuracy") 943 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 944 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 945 946 def alterset_sql(self, expression: exp.AlterSet) -> str: 947 exprs = self.expressions(expression, flat=True) 948 exprs = f" {exprs}" if exprs else "" 949 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 950 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 951 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 952 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 953 tag = self.expressions(expression, key="tag", flat=True) 954 tag = f" TAG {tag}" if tag else "" 955 956 return f"SET{exprs}{file_format}{copy_options}{tag}" 957 958 def strtotime_sql(self, expression: exp.StrToTime): 959 # target_type is stored as a DataType instance 960 target_type = expression.args.get("target_type") 961 962 # Get the type enum from DataType instance or from type annotation 963 if isinstance(target_type, exp.DataType): 964 type_enum = target_type.this 965 elif expression.type: 966 type_enum = expression.type.this 967 else: 968 type_enum = exp.DType.TIMESTAMP 969 970 func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP") 971 972 return self.func( 973 f"{'TRY_' if expression.args.get('safe') else ''}{func_name}", 974 expression.this, 975 self.format_time(expression), 976 ) 977 978 def timestampsub_sql(self, expression: exp.TimestampSub): 979 return self.sql( 980 exp.TimestampAdd( 981 this=expression.this, 982 expression=expression.expression * -1, 983 unit=expression.unit, 984 ) 985 ) 986 987 def jsonextract_sql(self, expression: exp.JSONExtract): 988 this = expression.this 989 990 # JSON strings are valid coming from other dialects such as BQ so 991 # for these cases we PARSE_JSON preemptively 992 if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get( 993 "requires_json" 994 ): 995 this = exp.ParseJSON(this=this) 996 997 return self.func( 998 "GET_PATH", 999 this, 1000 expression.expression, 1001 ) 1002 1003 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 1004 this = expression.this 1005 if this.is_string: 1006 this = exp.cast(this, exp.DType.TIMESTAMP) 1007 1008 return self.func("TO_CHAR", this, self.format_time(expression)) 1009 1010 def datesub_sql(self, expression: exp.DateSub) -> str: 1011 value = expression.expression 1012 if value: 1013 value.replace(value * (-1)) 1014 else: 1015 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1016 1017 return date_delta_sql("DATEADD")(self, expression) 1018 1019 def select_sql(self, expression: exp.Select) -> str: 1020 limit = expression.args.get("limit") 1021 offset = expression.args.get("offset") 1022 if offset and not limit: 1023 expression.limit(exp.Null(), copy=False) 1024 return super().select_sql(expression) 1025 1026 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1027 is_materialized = expression.find(exp.MaterializedProperty) 1028 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1029 1030 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1031 # For materialized views, COPY GRANTS is located *before* the columns list 1032 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1033 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1034 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1035 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1036 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1037 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1038 1039 this_name = self.sql(expression.this, "this") 1040 copy_grants = self.sql(copy_grants_property) 1041 this_schema = self.schema_columns_sql(expression.this) 1042 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1043 1044 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1045 1046 return super().createable_sql(expression, locations) 1047 1048 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1049 this = expression.this 1050 1051 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1052 # and add it later as part of the WITHIN GROUP clause 1053 order = this if isinstance(this, exp.Order) else None 1054 if order: 1055 expression.set("this", order.this.pop()) 1056 1057 expr_sql = super().arrayagg_sql(expression) 1058 1059 if order: 1060 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1061 1062 return expr_sql 1063 1064 def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str: 1065 if expression.args.get("check_null"): 1066 return self.func("ARRAY_DISTINCT", expression.this) 1067 return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this)) 1068 1069 def arraytostring_sql(self, expression: exp.ArrayToString) -> str: 1070 return self.func("ARRAY_TO_STRING", expression.this, expression.expression) 1071 1072 def array_sql(self, expression: exp.Array) -> str: 1073 expressions = expression.expressions 1074 1075 first_expr = seq_get(expressions, 0) 1076 if isinstance(first_expr, exp.Select): 1077 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1078 if first_expr.text("kind").upper() == "STRUCT": 1079 object_construct_args = [] 1080 for expr in first_expr.expressions: 1081 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1082 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1083 name = expr.this if isinstance(expr, exp.Alias) else expr 1084 1085 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1086 1087 array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args)) 1088 1089 first_expr.set("kind", None) 1090 first_expr.set("expressions", [array_agg]) 1091 1092 return self.sql(first_expr.subquery()) 1093 1094 return inline_array_sql(self, expression) 1095 1096 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1097 zone = self.sql(expression, "this") 1098 if not zone: 1099 return super().currentdate_sql(expression) 1100 1101 expr = exp.Cast( 1102 this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()), 1103 to=exp.DataType(this=exp.DType.DATE), 1104 ) 1105 return self.sql(expr) 1106 1107 def dot_sql(self, expression: exp.Dot) -> str: 1108 this = expression.this 1109 1110 if not this.type: 1111 from sqlglot.optimizer.annotate_types import annotate_types 1112 1113 this = annotate_types(this, dialect=self.dialect) 1114 1115 if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT): 1116 # Generate colon notation for the top level STRUCT 1117 return f"{self.sql(this)}:{self.sql(expression, 'expression')}" 1118 1119 return super().dot_sql(expression) 1120 1121 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 1122 return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}" 1123 1124 def format_sql(self, expression: exp.Format) -> str: 1125 if expression.name.lower() == "%s" and len(expression.expressions) == 1: 1126 return self.func("TO_CHAR", expression.expressions[0]) 1127 1128 return self.function_fallback_sql(expression) 1129 1130 def splitpart_sql(self, expression: exp.SplitPart) -> str: 1131 # Set part_index to 1 if missing 1132 if not expression.args.get("delimiter"): 1133 expression.set("delimiter", exp.Literal.string(" ")) 1134 1135 if not expression.args.get("part_index"): 1136 expression.set("part_index", exp.Literal.number(1)) 1137 1138 return rename_func("SPLIT_PART")(self, expression) 1139 1140 def uniform_sql(self, expression: exp.Uniform) -> str: 1141 gen = expression.args.get("gen") 1142 seed = expression.args.get("seed") 1143 1144 # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed) 1145 if seed: 1146 gen = exp.Rand(this=seed) 1147 1148 # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM() 1149 if not gen: 1150 gen = exp.Rand() 1151 1152 return self.func("UNIFORM", expression.this, expression.expression, gen) 1153 1154 def window_sql(self, expression: exp.Window) -> str: 1155 spec = expression.args.get("spec") 1156 this = expression.this 1157 1158 if ( 1159 ( 1160 isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1161 or ( 1162 isinstance(this, (exp.RespectNulls, exp.IgnoreNulls)) 1163 and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1164 ) 1165 ) 1166 and spec 1167 and ( 1168 spec.text("kind").upper() == "ROWS" 1169 and spec.text("start").upper() == "UNBOUNDED" 1170 and spec.text("start_side").upper() == "PRECEDING" 1171 and spec.text("end").upper() == "UNBOUNDED" 1172 and spec.text("end_side").upper() == "FOLLOWING" 1173 ) 1174 ): 1175 # omit the default window from window ranking functions 1176 expression.set("spec", None) 1177 return super().window_sql(expression) 1178 1179 def filter_sql(self, expression: exp.Filter) -> str: 1180 # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an 1181 # equivalent conditional aggregation, i.e. wrap the input values in an IFF 1182 agg = expression.this 1183 agg_arg = agg.this 1184 cond = expression.expression.this 1185 1186 if isinstance(agg, exp.WithinGroup): 1187 # Ordered-set aggregates take their input from the ORDER BY key, so the 1188 # condition has to wrap that instead of the aggregate's own argument 1189 if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)): 1190 for ordered in agg.expression.expressions: 1191 key = ordered.this 1192 key.replace(exp.If(this=cond.copy(), true=key.copy())) 1193 1194 return self.sql(agg) 1195 1196 # Besides the percentile functions, these are the only functions Snowflake 1197 # accepts WITHIN GROUP for, so anything else can't be rewritten correctly 1198 if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)): 1199 agg_arg = agg_arg.this 1200 else: 1201 self.unsupported("Unable to rewrite FILTER into the aggregate's arguments") 1202 return self.sql(agg) 1203 1204 # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF 1205 # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of 1206 # them, which Snowflake rejects. Use its native COUNT_IF instead. 1207 if isinstance(agg, exp.Count) and agg_arg.is_star: 1208 return self.func("COUNT_IF", cond) 1209 1210 # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the 1211 # condition has to wrap the values underneath them rather than the whole clause -- 1212 # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts. 1213 if isinstance(agg_arg, exp.Order): 1214 agg_arg = agg_arg.this 1215 1216 if isinstance(agg_arg, exp.Distinct): 1217 targets = agg_arg.expressions 1218 else: 1219 targets = [agg_arg] 1220 1221 for target in targets: 1222 target.replace(exp.If(this=cond.copy(), true=target.copy())) 1223 1224 return self.sql(agg) 1225 1226 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1227 # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only 1228 # accepts the value to aggregate as an argument: MODE(<expr>) 1229 if isinstance(expression.this, exp.Mode) and not expression.this.this: 1230 order = expression.expression 1231 if isinstance(order, exp.Order) and len(order.expressions) == 1: 1232 return self.sql(exp.Mode(this=order.expressions[0].this)) 1233 1234 return super().withingroup_sql(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: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
TRANSFORMS =
{<class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ApproxDistinct'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArgMax'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArgMin'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Array'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.array.ArrayConcat'>: <function array_concat_sql.<locals>._array_concat_sql>, <class 'sqlglot.expressions.array.ArrayAppend'>: <function array_append_sql.<locals>._array_append_sql>, <class 'sqlglot.expressions.array.ArrayPrepend'>: <function array_append_sql.<locals>._array_append_sql>, <class 'sqlglot.expressions.array.ArrayContains'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.ArrayPosition'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.ArrayIntersect'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.AtTimeZone'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseOr'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseXor'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseAnd'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.BitwiseAndAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.BitwiseOrAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.BitwiseXorAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseNot'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseLeftShift'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseRightShift'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.ddl.Create'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.CurrentTimestamp'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentSchemas'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Localtime'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Localtimestamp'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DateAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DateDiff'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DatetimeAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DatetimeDiff'>: <function timestampdiff_sql>, <class 'sqlglot.expressions.temporal.DateStrToDate'>: <function datestrtodate_sql>, <class 'sqlglot.expressions.string.Decrypt'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.DecryptRaw'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfMonth'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfWeek'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfWeekIso'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfYear'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.DotProduct'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Explode'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.Extract'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.math.CosineDistance'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.EuclideanDistance'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.core.FromTimeZone'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.GenerateSeries'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.GetExtract'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.GroupConcat'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.If'>: <function if_sql.<locals>._if_sql>, <class 'sqlglot.expressions.json.JSONArray'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONExtractArray'>: <function _json_extract_value_array_sql>, <class 'sqlglot.expressions.json.JSONExtractScalar'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONKeys'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.query.JSONValueArray'>: <function _json_extract_value_array_sql>, <class 'sqlglot.expressions.string.Levenshtein'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.LogicalAnd'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.LogicalOr'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Map'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.math.ManhattanDistance'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.MakeInterval'>: <function no_make_interval_sql>, <class 'sqlglot.expressions.aggregate.Max'>: <function max_or_greatest>, <class 'sqlglot.expressions.aggregate.Min'>: <function min_or_least>, <class 'sqlglot.expressions.json.ParseJSON'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.ToBinary'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.ToBoolean'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.ToDouble'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.ToFile'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONFormat'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.PercentileCont'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.aggregate.PercentileDisc'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.query.Pivot'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.string.RegexpExtract'>: <function _regexpextract_sql>, <class 'sqlglot.expressions.string.RegexpExtractAll'>: <function _regexpextract_sql>, <class 'sqlglot.expressions.string.RegexpILike'>: <function _regexpilike_sql>, <class 'sqlglot.expressions.query.Select'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.string.SHA'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA1Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.MD5Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.MD5NumberLower64'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.MD5NumberUpper64'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Hex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.LowerHex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.Skewness'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.StarMap'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.StartsWith'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.EndsWith'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.Rand'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.StrPosition'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.StrToDate'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.StringToArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.StrtokToArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Stuff'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.StPoint'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TimeAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TimeSlice'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Timestamp'>: <function no_timestamp_sql>, <class 'sqlglot.expressions.temporal.TimestampAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TimestampDiff'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TimestampTrunc'>: <function timestamptrunc_sql.<locals>._timestamptrunc_sql>, <class 'sqlglot.expressions.temporal.TimeStrToTime'>: <function timestrtotime_sql>, <class 'sqlglot.expressions.temporal.TimeToUnix'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.ToArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.ToChar'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TsOrDsDiff'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TsOrDsToDate'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsToTime'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.Unhex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.UnixToTime'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.Uuid'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.Booland'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.Boolor'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.WeekOfYear'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.YearOfWeek'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.YearOfWeekIso'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.Xor'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.ByteLength'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Flatten'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArrayConcatAgg'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.SHA2Digest'>: <function SnowflakeGenerator.<lambda>>}
619 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 620 this = self.func("IDENTIFIER", expression.this) 621 if "expressions" in expression.args: 622 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 623 return self.func(this, *expression.expressions, normalize=False) 624 return this
626 def sortarray_sql(self, expression: exp.SortArray) -> str: 627 asc = expression.args.get("asc") 628 nulls_first = expression.args.get("nulls_first") 629 if asc == exp.false() and nulls_first == exp.true(): 630 nulls_first = None 631 return self.func("ARRAY_SORT", expression.this, asc, nulls_first)
633 def nthvalue_sql(self, expression: exp.NthValue) -> str: 634 result = self.func("NTH_VALUE", expression.this, expression.args.get("offset")) 635 636 from_first = expression.args.get("from_first") 637 638 if from_first is not None: 639 if from_first: 640 result = result + " FROM FIRST" 641 else: 642 result = result + " FROM LAST" 643 644 return result
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>}
TYPE_MAPPING =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP', <DType.BIGDECIMAL: 'BIGDECIMAL'>: 'DOUBLE', <DType.JSON: 'JSON'>: 'VARIANT', <DType.NESTED: 'NESTED'>: 'OBJECT', <DType.STRUCT: 'STRUCT'>: 'OBJECT', <DType.TEXT: 'TEXT'>: 'VARCHAR'}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>}
UNSUPPORTED_VALUES_EXPRESSIONS =
{<class 'sqlglot.expressions.array.VarMap'>, <class 'sqlglot.expressions.array.Struct'>, <class 'sqlglot.expressions.array.Map'>, <class 'sqlglot.expressions.array.StarMap'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS =
(<class 'sqlglot.expressions.aggregate.ArrayAgg'>,)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
693 def datatype_sql(self, expression: exp.DataType) -> str: 694 # Check if this is a FLOAT type nested inside a VECTOR type 695 # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types 696 # https://docs.snowflake.com/en/sql-reference/data-types-vector 697 if expression.is_type(exp.DType.DOUBLE): 698 parent = expression.parent 699 if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR): 700 # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE 701 return "FLOAT" 702 703 expressions = expression.expressions 704 if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES): 705 for field_type in expressions: 706 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 707 if isinstance(field_type, exp.DataType): 708 return "OBJECT" 709 if ( 710 isinstance(field_type, exp.ColumnDef) 711 and field_type.this 712 and field_type.this.is_string 713 ): 714 # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides 715 # converting 'foo' into an identifier, we also need to quote it because these 716 # keys are case-sensitive. For example: 717 # 718 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct 719 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL 720 field_type.this.replace(exp.to_identifier(field_type.name, quoted=True)) 721 722 return super().datatype_sql(expression)
724 def tonumber_sql(self, expression: exp.ToNumber) -> str: 725 precision = expression.args.get("precision") 726 scale = expression.args.get("scale") 727 728 default_precision = isinstance(precision, exp.Literal) and precision.name == "38" 729 default_scale = isinstance(scale, exp.Literal) and scale.name == "0" 730 731 if default_precision and default_scale: 732 precision = None 733 scale = None 734 elif default_scale: 735 scale = None 736 737 func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER" 738 739 return self.func( 740 func_name, 741 expression.this, 742 expression.args.get("format"), 743 precision, 744 scale, 745 )
def
timestampfromparts_sql(self, expression: sqlglot.expressions.temporal.TimestampFromParts) -> str:
747 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 748 milli = expression.args.get("milli") 749 if milli is not None: 750 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 751 expression.set("nano", milli_to_nano) 752 753 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
755 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 756 if expression.is_type(exp.DType.GEOGRAPHY): 757 return self.func("TO_GEOGRAPHY", expression.this) 758 if expression.is_type(exp.DType.GEOMETRY): 759 return self.func("TO_GEOMETRY", expression.this) 760 761 return super().cast_sql(expression, safe_prefix=safe_prefix)
763 def trycast_sql(self, expression: exp.TryCast) -> str: 764 value = expression.this 765 766 if value.type is None: 767 from sqlglot.optimizer.annotate_types import annotate_types 768 769 value = annotate_types(value, dialect=self.dialect) 770 771 # Snowflake requires that TRY_CAST's value be a string 772 # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or 773 # if we can deduce that the value is a string, then we can generate TRY_CAST 774 if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES): 775 return super().trycast_sql(expression) 776 777 return self.cast_sql(expression)
793 def generator_sql(self, expression: exp.Generator) -> str: 794 args = [] 795 rowcount = expression.args.get("rowcount") 796 timelimit = expression.args.get("timelimit") 797 798 if rowcount: 799 args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount)) 800 if timelimit: 801 args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit)) 802 803 return self.func("GENERATOR", *args)
805 def unnest_sql(self, expression: exp.Unnest) -> str: 806 unnest_alias = expression.args.get("alias") 807 offset = expression.args.get("offset") 808 809 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 810 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 811 812 columns = [ 813 exp.to_identifier("seq"), 814 exp.to_identifier("key"), 815 exp.to_identifier("path"), 816 offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"), 817 value, 818 exp.to_identifier("this"), 819 ] 820 821 if unnest_alias: 822 unnest_alias.set("columns", columns) 823 else: 824 unnest_alias = exp.TableAlias(this="_u", columns=columns) 825 826 table_input = self.sql(expression.expressions[0]) 827 if not table_input.startswith("INPUT =>"): 828 table_input = f"INPUT => {table_input}" 829 830 expression_parent = expression.parent 831 832 explode = ( 833 f"FLATTEN({table_input})" 834 if isinstance(expression_parent, exp.Lateral) 835 else f"TABLE(FLATTEN({table_input}))" 836 ) 837 alias = self.sql(unnest_alias) 838 alias = f" AS {alias}" if alias else "" 839 value = ( 840 "" 841 if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral)) 842 else f"{value} FROM " 843 ) 844 845 return f"{value}{explode}{alias}"
854 def show_sql(self, expression: exp.Show) -> str: 855 terse = "TERSE " if expression.args.get("terse") else "" 856 iceberg = "ICEBERG " if expression.args.get("iceberg") else "" 857 history = " HISTORY" if expression.args.get("history") else "" 858 like = self.sql(expression, "like") 859 like = f" LIKE {like}" if like else "" 860 861 scope = self.sql(expression, "scope") 862 scope = f" {scope}" if scope else "" 863 864 scope_kind = self.sql(expression, "scope_kind") 865 if scope_kind: 866 scope_kind = f" IN {scope_kind}" 867 868 starts_with = self.sql(expression, "starts_with") 869 if starts_with: 870 starts_with = f" STARTS WITH {starts_with}" 871 872 limit = self.sql(expression, "limit") 873 874 from_ = self.sql(expression, "from_") 875 if from_: 876 from_ = f" FROM {from_}" 877 878 privileges = self.expressions(expression, key="privileges", flat=True) 879 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 880 881 return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}"
def
rowaccessproperty_sql( self, expression: sqlglot.expressions.properties.RowAccessProperty) -> str:
889 def describe_sql(self, expression: exp.Describe) -> str: 890 kind_value = expression.args.get("kind") or "TABLE" 891 892 properties = expression.args.get("properties") 893 if properties: 894 qualifier = self.expressions(properties, sep=" ") 895 kind = f" {qualifier} {kind_value}" 896 else: 897 kind = f" {kind_value}" 898 899 this = f" {self.sql(expression, 'this')}" 900 expressions = self.expressions(expression, flat=True) 901 expressions = f" {expressions}" if expressions else "" 902 return f"DESCRIBE{kind}{this}{expressions}"
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
904 def generatedasidentitycolumnconstraint_sql( 905 self, expression: exp.GeneratedAsIdentityColumnConstraint 906 ) -> str: 907 start = expression.args.get("start") 908 start = f" START {start}" if start else "" 909 increment = expression.args.get("increment") 910 increment = f" INCREMENT {increment}" if increment else "" 911 912 order = expression.args.get("order") 913 if order is not None: 914 order_clause = " ORDER" if order else " NOORDER" 915 else: 916 order_clause = "" 917 918 return f"AUTOINCREMENT{start}{increment}{order_clause}"
920 def struct_sql(self, expression: exp.Struct) -> str: 921 if len(expression.expressions) == 1: 922 arg = expression.expressions[0] 923 if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star): 924 # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object 925 return f"{{{self.sql(expression.expressions[0])}}}" 926 927 keys = [] 928 values = [] 929 930 for i, e in enumerate(expression.expressions): 931 if isinstance(e, exp.PropertyEQ): 932 keys.append( 933 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 934 ) 935 values.append(e.expression) 936 else: 937 keys.append(exp.Literal.string(f"_{i}")) 938 values.append(e) 939 940 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
@unsupported_args('weight', 'accuracy')
def
approxquantile_sql(self, expression: sqlglot.expressions.aggregate.ApproxQuantile) -> str:
946 def alterset_sql(self, expression: exp.AlterSet) -> str: 947 exprs = self.expressions(expression, flat=True) 948 exprs = f" {exprs}" if exprs else "" 949 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 950 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 951 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 952 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 953 tag = self.expressions(expression, key="tag", flat=True) 954 tag = f" TAG {tag}" if tag else "" 955 956 return f"SET{exprs}{file_format}{copy_options}{tag}"
958 def strtotime_sql(self, expression: exp.StrToTime): 959 # target_type is stored as a DataType instance 960 target_type = expression.args.get("target_type") 961 962 # Get the type enum from DataType instance or from type annotation 963 if isinstance(target_type, exp.DataType): 964 type_enum = target_type.this 965 elif expression.type: 966 type_enum = expression.type.this 967 else: 968 type_enum = exp.DType.TIMESTAMP 969 970 func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP") 971 972 return self.func( 973 f"{'TRY_' if expression.args.get('safe') else ''}{func_name}", 974 expression.this, 975 self.format_time(expression), 976 )
987 def jsonextract_sql(self, expression: exp.JSONExtract): 988 this = expression.this 989 990 # JSON strings are valid coming from other dialects such as BQ so 991 # for these cases we PARSE_JSON preemptively 992 if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get( 993 "requires_json" 994 ): 995 this = exp.ParseJSON(this=this) 996 997 return self.func( 998 "GET_PATH", 999 this, 1000 expression.expression, 1001 )
1010 def datesub_sql(self, expression: exp.DateSub) -> str: 1011 value = expression.expression 1012 if value: 1013 value.replace(value * (-1)) 1014 else: 1015 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 1016 1017 return date_delta_sql("DATEADD")(self, expression)
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1026 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1027 is_materialized = expression.find(exp.MaterializedProperty) 1028 copy_grants_property = expression.find(exp.CopyGrantsProperty) 1029 1030 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1031 # For materialized views, COPY GRANTS is located *before* the columns list 1032 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1033 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1034 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1035 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1036 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1037 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1038 1039 this_name = self.sql(expression.this, "this") 1040 copy_grants = self.sql(copy_grants_property) 1041 this_schema = self.schema_columns_sql(expression.this) 1042 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1043 1044 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1045 1046 return super().createable_sql(expression, locations)
1048 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1049 this = expression.this 1050 1051 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1052 # and add it later as part of the WITHIN GROUP clause 1053 order = this if isinstance(this, exp.Order) else None 1054 if order: 1055 expression.set("this", order.this.pop()) 1056 1057 expr_sql = super().arrayagg_sql(expression) 1058 1059 if order: 1060 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1061 1062 return expr_sql
1072 def array_sql(self, expression: exp.Array) -> str: 1073 expressions = expression.expressions 1074 1075 first_expr = seq_get(expressions, 0) 1076 if isinstance(first_expr, exp.Select): 1077 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1078 if first_expr.text("kind").upper() == "STRUCT": 1079 object_construct_args = [] 1080 for expr in first_expr.expressions: 1081 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1082 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1083 name = expr.this if isinstance(expr, exp.Alias) else expr 1084 1085 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1086 1087 array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args)) 1088 1089 first_expr.set("kind", None) 1090 first_expr.set("expressions", [array_agg]) 1091 1092 return self.sql(first_expr.subquery()) 1093 1094 return inline_array_sql(self, expression)
1096 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1097 zone = self.sql(expression, "this") 1098 if not zone: 1099 return super().currentdate_sql(expression) 1100 1101 expr = exp.Cast( 1102 this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()), 1103 to=exp.DataType(this=exp.DType.DATE), 1104 ) 1105 return self.sql(expr)
1107 def dot_sql(self, expression: exp.Dot) -> str: 1108 this = expression.this 1109 1110 if not this.type: 1111 from sqlglot.optimizer.annotate_types import annotate_types 1112 1113 this = annotate_types(this, dialect=self.dialect) 1114 1115 if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT): 1116 # Generate colon notation for the top level STRUCT 1117 return f"{self.sql(this)}:{self.sql(expression, 'expression')}" 1118 1119 return super().dot_sql(expression)
1130 def splitpart_sql(self, expression: exp.SplitPart) -> str: 1131 # Set part_index to 1 if missing 1132 if not expression.args.get("delimiter"): 1133 expression.set("delimiter", exp.Literal.string(" ")) 1134 1135 if not expression.args.get("part_index"): 1136 expression.set("part_index", exp.Literal.number(1)) 1137 1138 return rename_func("SPLIT_PART")(self, expression)
1140 def uniform_sql(self, expression: exp.Uniform) -> str: 1141 gen = expression.args.get("gen") 1142 seed = expression.args.get("seed") 1143 1144 # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed) 1145 if seed: 1146 gen = exp.Rand(this=seed) 1147 1148 # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM() 1149 if not gen: 1150 gen = exp.Rand() 1151 1152 return self.func("UNIFORM", expression.this, expression.expression, gen)
1154 def window_sql(self, expression: exp.Window) -> str: 1155 spec = expression.args.get("spec") 1156 this = expression.this 1157 1158 if ( 1159 ( 1160 isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1161 or ( 1162 isinstance(this, (exp.RespectNulls, exp.IgnoreNulls)) 1163 and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1164 ) 1165 ) 1166 and spec 1167 and ( 1168 spec.text("kind").upper() == "ROWS" 1169 and spec.text("start").upper() == "UNBOUNDED" 1170 and spec.text("start_side").upper() == "PRECEDING" 1171 and spec.text("end").upper() == "UNBOUNDED" 1172 and spec.text("end_side").upper() == "FOLLOWING" 1173 ) 1174 ): 1175 # omit the default window from window ranking functions 1176 expression.set("spec", None) 1177 return super().window_sql(expression)
1179 def filter_sql(self, expression: exp.Filter) -> str: 1180 # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an 1181 # equivalent conditional aggregation, i.e. wrap the input values in an IFF 1182 agg = expression.this 1183 agg_arg = agg.this 1184 cond = expression.expression.this 1185 1186 if isinstance(agg, exp.WithinGroup): 1187 # Ordered-set aggregates take their input from the ORDER BY key, so the 1188 # condition has to wrap that instead of the aggregate's own argument 1189 if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)): 1190 for ordered in agg.expression.expressions: 1191 key = ordered.this 1192 key.replace(exp.If(this=cond.copy(), true=key.copy())) 1193 1194 return self.sql(agg) 1195 1196 # Besides the percentile functions, these are the only functions Snowflake 1197 # accepts WITHIN GROUP for, so anything else can't be rewritten correctly 1198 if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)): 1199 agg_arg = agg_arg.this 1200 else: 1201 self.unsupported("Unable to rewrite FILTER into the aggregate's arguments") 1202 return self.sql(agg) 1203 1204 # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF 1205 # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of 1206 # them, which Snowflake rejects. Use its native COUNT_IF instead. 1207 if isinstance(agg, exp.Count) and agg_arg.is_star: 1208 return self.func("COUNT_IF", cond) 1209 1210 # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the 1211 # condition has to wrap the values underneath them rather than the whole clause -- 1212 # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts. 1213 if isinstance(agg_arg, exp.Order): 1214 agg_arg = agg_arg.this 1215 1216 if isinstance(agg_arg, exp.Distinct): 1217 targets = agg_arg.expressions 1218 else: 1219 targets = [agg_arg] 1220 1221 for target in targets: 1222 target.replace(exp.If(this=cond.copy(), true=target.copy())) 1223 1224 return self.sql(agg)
1226 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1227 # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only 1228 # accepts the value to aggregate as an argument: MODE(<expr>) 1229 if isinstance(expression.this, exp.Mode) and not expression.this.this: 1230 order = expression.expression 1231 if isinstance(order, exp.Order) and len(order.expressions) == 1: 1232 return self.sql(exp.Mode(this=order.expressions[0].this)) 1233 1234 return super().withingroup_sql(expression)
Inherited Members
- sqlglot.generator.Generator
- Generator
- NULL_ORDERING_SUPPORTED
- WINDOW_FUNCS_WITH_NULL_ORDERING
- IGNORE_NULLS_IN_FUNC
- IGNORE_NULLS_BEFORE_ORDER
- LOCKING_READS_SUPPORTED
- WRAP_DERIVED_VALUES
- CREATE_FUNCTION_RETURN_AS
- SUPPORTS_MERGE_WHERE
- INTERVAL_ALLOWS_PLURAL_FORM
- AUTO_REFRESH_BARE_INTERVALS
- LIMIT_FETCH
- RENAME_TABLE_WITH_DB
- GROUPINGS_SEP
- INDEX_ON
- INOUT_SEPARATOR
- QUERY_HINT_SEP
- 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
- HISTORICAL_DATA_POST_ALIAS
- DATA_TYPE_SPECIFIERS_ALLOWED
- ENSURE_BOOLS
- CTE_RECURSIVE_KEYWORD_REQUIRED
- SUPPORTS_SINGLE_ARG_CONCAT
- LAST_DAY_SUPPORTS_DATE_PART
- SUPPORTS_TABLE_ALIAS_COLUMNS
- SUPPORTS_NAMED_CTE_COLUMNS
- UNPIVOT_ALIASES_ARE_IDENTIFIERS
- PIVOT_ALIAS_WITH_AS
- SUPPORTS_SELECT_INTO
- SUPPORTS_UNLOGGED_TABLES
- SUPPORTS_CREATE_TABLE_LIKE
- SUPPORTS_MODIFY_COLUMN
- SUPPORTS_CHANGE_COLUMN
- LIKE_PROPERTY_INSIDE_SCHEMA
- MULTI_ARG_DISTINCT
- JSON_TYPE_REQUIRED_FOR_EXTRACTION
- JSON_PATH_BRACKETED_KEY_SUPPORTED
- JSON_PATH_SINGLE_QUOTE_ESCAPE
- JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
- CAN_IMPLEMENT_ARRAY_ANY
- SUPPORTS_TO_NUMBER
- SUPPORTS_WINDOW_EXCLUDE
- SET_OP_MODIFIERS
- COPY_HAS_INTO_KEYWORD
- UNICODE_SUBSTITUTE
- HEX_FUNC
- WITH_PROPERTIES_PREFIX
- QUOTE_JSON_PATH
- PAD_FILL_PATTERN_IS_REQUIRED
- SUPPORTS_UNIX_SECONDS
- ALTER_SET_WRAPPED
- NORMALIZE_EXTRACT_DATE_PARTS
- PARSE_JSON_NAME
- ALTER_SET_TYPE
- ARRAY_SIZE_DIM_REQUIRED
- SUPPORTS_BETWEEN_FLAGS
- SUPPORTS_LIKE_QUANTIFIERS
- MATCH_AGAINST_TABLE_PREFIX
- SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD
- DECLARE_DEFAULT_ASSIGNMENT
- UPDATE_STATEMENT_SUPPORTS_FROM
- STAR_EXCLUDE_REQUIRES_DERIVED_TABLE
- SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
- UNSUPPORTED_TYPES
- TYPE_PARAM_SETTINGS
- 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
- SAFE_JSON_PATH_KEY_RE
- SENTINEL_LINE_BREAK
- pretty
- identify
- normalize
- pad
- unsupported_level
- max_unsupported
- leading_comma
- max_text_width
- comments
- dialect
- normalize_functions
- unsupported_messages
- generate
- preprocess
- unsupported
- sep
- seg
- sanitize_comment
- maybe_comment
- wrap
- no_identify
- normalize_func
- indent
- sql
- uncache_sql
- cache_sql
- characterset_sql
- column_parts
- column_sql
- pseudocolumn_sql
- columnposition_sql
- columndef_sql
- columnconstraint_sql
- computedcolumnconstraint_sql
- autoincrementcolumnconstraint_sql
- compresscolumnconstraint_sql
- generatedasrowcolumnconstraint_sql
- periodforsystemtimeconstraint_sql
- notnullcolumnconstraint_sql
- primarykeycolumnconstraint_sql
- uniquecolumnconstraint_sql
- inoutcolumnconstraint_sql
- create_sql
- sequenceproperties_sql
- triggerproperties_sql
- triggerreferencing_sql
- triggerevent_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
- datatype_param_bound_limiter
- directory_sql
- delete_sql
- drop_sql
- set_operation
- set_operations
- fetch_sql
- limitoptions_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
- uuidproperty_sql
- likeproperty_sql
- fallbackproperty_sql
- journalproperty_sql
- freespaceproperty_sql
- checksumproperty_sql
- mergeblockratioproperty_sql
- moduleproperty_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
- rollupindex_sql
- rollupproperty_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
- queryband_sql
- pragma_sql
- lock_sql
- literal_sql
- escape_str
- loaddata_sql
- null_sql
- boolean_sql
- booland_sql
- boolor_sql
- order_sql
- withfill_sql
- cluster_sql
- clusterproperty_sql
- distribute_sql
- sort_sql
- ordered_sql
- matchrecognizemeasure_sql
- matchrecognize_sql
- query_modifiers
- options_modifier
- forclause_sql
- 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
- partition_by_sql
- windowspec_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
- timeserieskey_sql
- if_sql
- matchagainst_sql
- jsonkeyvalue_sql
- jsonpath_sql
- json_path_part
- formatjson_sql
- formatphrase_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
- fromiso8601date_sql
- fromiso8601timestamp_sql
- fromiso8601timestampnanos_sql
- add_sql
- and_sql
- or_sql
- xor_sql
- connector_sql
- bitwiseand_sql
- bitwiseleftshift_sql
- bitwisenot_sql
- bitwiseor_sql
- bitwiserightshift_sql
- bitwisexor_sql
- strtodate_sql
- parsedatetime_sql
- collate_sql
- command_sql
- comment_sql
- mergetreettlaction_sql
- mergetreettl_sql
- transaction_sql
- commit_sql
- rollback_sql
- altercolumn_sql
- modifycolumn_sql
- alterindex_sql
- alterdiststyle_sql
- altersortkey_sql
- alterrename_sql
- renamecolumn_sql
- alter_sql
- altersession_sql
- add_column_sql
- droppartition_sql
- dropprimarykey_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
- distancend_sql
- eq_sql
- propertyeq_sql
- escape_sql
- glob_sql
- gt_sql
- gte_sql
- is_sql
- like_sql
- ilike_sql
- match_sql
- similarto_sql
- lt_sql
- lte_sql
- mod_sql
- mul_sql
- neq_sql
- nullsafeeq_sql
- nullsafeneq_sql
- sub_sql
- 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
- macrooverloads_sql
- macrooverload_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
- generateembedding_sql
- generatetext_sql
- generatetable_sql
- generatebool_sql
- generateint_sql
- generatedouble_sql
- mltranslate_sql
- mlforecast_sql
- aiforecast_sql
- featuresattime_sql
- vectorsearch_sql
- forin_sql
- refresh_sql
- toarray_sql
- tsordstotime_sql
- tsordstotimestamp_sql
- tsordstodatetime_sql
- tsordstodate_sql
- unixdate_sql
- lastday_sql
- dateadd_sql
- arrayany_sql
- partitionrange_sql
- truncatetable_sql
- convert_sql
- copyparameter_sql
- credentials_sql
- copy_sql
- semicolon_sql
- datadeletionproperty_sql
- maskingpolicycolumnconstraint_sql
- gapfill_sql
- scope_resolution
- scoperesolution_sql
- parsejson_sql
- rand_sql
- changes_sql
- pad_sql
- summarize_sql
- explodinggenerateseries_sql
- converttimezone_sql
- json_sql
- jsonvalue_sql
- skipjsoncolumn_sql
- conditionalinsert_sql
- multitableinserts_sql
- oncondition_sql
- jsonextractquote_sql
- jsonexists_sql
- slice_sql
- apply_sql
- grant_sql
- revoke_sql
- grantprivilege_sql
- grantprincipal_sql
- columns_sql
- overlay_sql
- todouble_sql
- string_sql
- median_sql
- overflowtruncatebehavior_sql
- unixseconds_sql
- arraysize_sql
- attach_sql
- detach_sql
- attachoption_sql
- watermarkcolumnconstraint_sql
- encodeproperty_sql
- includeproperty_sql
- xmlelement_sql
- xmlkeyvalueoption_sql
- partitionbyrangeproperty_sql
- partitionbyrangepropertydynamic_sql
- unpivotcolumns_sql
- analyzesample_sql
- analyzestatistics_sql
- analyzehistogram_sql
- analyzedelete_sql
- analyzelistchainedrows_sql
- analyzevalidate_sql
- analyze_sql
- xmltable_sql
- xmlnamespace_sql
- export_sql
- declare_sql
- declareitem_sql
- recursivewithsearch_sql
- parameterizedagg_sql
- anonymousaggfunc_sql
- combinedaggfunc_sql
- combinedparameterizedagg_sql
- install_sql
- get_put_sql
- translatecharacters_sql
- decodecase_sql
- semanticview_sql
- getextract_sql
- datefromunixdate_sql
- space_sql
- buildproperty_sql
- refreshtriggerproperty_sql
- directorystage_sql
- uuid_sql
- initcap_sql
- localtime_sql
- localtimestamp_sql
- weekstart_name
- weekstart_sql
- chr_sql
- block_sql
- functionspecification_sql
- storedprocedure_sql
- ifblock_sql
- whileblock_sql
- execute_sql
- executesql_sql
- altermodifysqlsecurity_sql
- usingproperty_sql
- renameindex_sql