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 nth_value_from_sql, 21 rename_func, 22 strposition_sql, 23 timestampdiff_sql, 24 timestamptrunc_sql, 25 timestrtotime_sql, 26 unit_to_str, 27 var_map_sql, 28) 29from sqlglot.generator import unsupported_args 30from sqlglot.helper import find_new_name, flatten, seq_get 31from sqlglot.optimizer.scope import build_scope, find_all_in_scope 32from sqlglot.parsers.snowflake import ( 33 RANKING_WINDOW_FUNCTIONS_WITH_FRAME, 34 TIMESTAMP_TYPES, 35 SnowflakeParser, 36 build_object_construct, 37) 38from sqlglot.tokens import TokenType 39 40if t.TYPE_CHECKING: 41 from sqlglot._typing import E 42 43 44def _build_datediff(args: list) -> exp.DateDiff: 45 return exp.DateDiff( 46 this=seq_get(args, 2), 47 expression=seq_get(args, 1), 48 unit=map_date_part(seq_get(args, 0)), 49 date_part_boundary=True, 50 ) 51 52 53def _build_date_time_add(expr_type: type[E]) -> t.Callable[[list], E]: 54 def _builder(args: list) -> E: 55 return expr_type( 56 this=seq_get(args, 2), 57 expression=seq_get(args, 1), 58 unit=map_date_part(seq_get(args, 0)), 59 ) 60 61 return _builder 62 63 64def _regexpilike_sql(self: SnowflakeGenerator, expression: exp.RegexpILike) -> str: 65 flag = expression.text("flag") 66 67 if "i" not in flag: 68 flag += "i" 69 70 return self.func( 71 "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag) 72 ) 73 74 75def _unqualify_pivot_columns(expression: exp.Expr) -> exp.Expr: 76 """ 77 Snowflake doesn't allow columns referenced in UNPIVOT to be qualified, 78 so we need to unqualify them. Same goes for ANY ORDER BY <column>. 79 80 Example: 81 >>> from sqlglot import parse_one 82 >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))") 83 >>> print(_unqualify_pivot_columns(expr).sql(dialect="snowflake")) 84 SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april)) 85 """ 86 if isinstance(expression, exp.Pivot): 87 if expression.unpivot: 88 expression = transforms.unqualify_columns(expression) 89 else: 90 for field in expression.fields: 91 field_expr = seq_get(field.expressions if field else [], 0) 92 93 if isinstance(field_expr, exp.PivotAny): 94 unqualified_field_expr = transforms.unqualify_columns(field_expr) 95 t.cast(exp.Expr, field).set("expressions", unqualified_field_expr, 0) 96 97 return expression 98 99 100def _unnest_generate_date_array(unnest: exp.Unnest) -> None: 101 generate_date_array = unnest.expressions[0] 102 start = generate_date_array.args.get("start") 103 end = generate_date_array.args.get("end") 104 step = generate_date_array.args.get("step") 105 106 if not start or not end or not isinstance(step, exp.Interval) or step.name != "1": 107 return 108 109 unit = step.args.get("unit") 110 111 unnest_alias = unnest.args.get("alias") 112 if unnest_alias: 113 unnest_alias = unnest_alias.copy() 114 sequence_value_name = seq_get(unnest_alias.columns, 0) or "value" 115 else: 116 sequence_value_name = "value" 117 118 # We'll add the next sequence value to the starting date and project the result 119 date_add = _build_date_time_add(exp.DateAdd)( 120 [unit, exp.cast(sequence_value_name, "int"), exp.cast(start, "date")] 121 ) 122 123 # We use DATEDIFF to compute the number of sequence values needed 124 number_sequence = SnowflakeParser.FUNCTIONS["ARRAY_GENERATE_RANGE"]( 125 [exp.Literal.number(0), _build_datediff([unit, start, end]) + 1] 126 ) 127 128 unnest.set("expressions", [number_sequence]) 129 130 unnest_parent = unnest.parent 131 if isinstance(unnest_parent, exp.Join): 132 select = unnest_parent.parent 133 if isinstance(select, exp.Select): 134 replace_column_name = ( 135 sequence_value_name 136 if isinstance(sequence_value_name, str) 137 else sequence_value_name.name 138 ) 139 140 scope = build_scope(select) 141 if scope: 142 for column in scope.columns: 143 if column.name.lower() == replace_column_name.lower(): 144 column.replace( 145 date_add.as_(replace_column_name) 146 if isinstance(column.parent, exp.Select) 147 else date_add 148 ) 149 150 lateral = exp.Lateral(this=unnest_parent.this.pop()) 151 unnest_parent.replace(exp.Join(this=lateral)) 152 else: 153 unnest.replace( 154 exp.select(date_add.as_(sequence_value_name)) 155 .from_(unnest.copy()) 156 .subquery(unnest_alias) 157 ) 158 159 160def _transform_generate_date_array(expression: exp.Expr) -> exp.Expr: 161 if isinstance(expression, exp.Select): 162 for generate_date_array in expression.find_all(exp.GenerateDateArray): 163 parent = generate_date_array.parent 164 165 # If GENERATE_DATE_ARRAY is used directly as an array (e.g passed into ARRAY_LENGTH), the transformed Snowflake 166 # query is the following (it'll be unnested properly on the next iteration due to copy): 167 # SELECT ref(GENERATE_DATE_ARRAY(...)) -> SELECT ref((SELECT ARRAY_AGG(*) FROM UNNEST(GENERATE_DATE_ARRAY(...)))) 168 if not isinstance(parent, exp.Unnest): 169 unnest = exp.Unnest(expressions=[generate_date_array.copy()]) 170 generate_date_array.replace( 171 exp.select(exp.ArrayAgg(this=exp.Star())).from_(unnest).subquery() 172 ) 173 174 if ( 175 isinstance(parent, exp.Unnest) 176 and isinstance(parent.parent, (exp.From, exp.Join)) 177 and len(parent.expressions) == 1 178 ): 179 _unnest_generate_date_array(parent) 180 181 return expression 182 183 184def _regexpextract_sql( 185 self: SnowflakeGenerator, expression: exp.RegexpExtract | exp.RegexpExtractAll 186) -> str: 187 # Other dialects don't support all of the following parameters, so we need to 188 # generate default values as necessary to ensure the transpilation is correct 189 group = expression.args.get("group") 190 191 # To avoid generating all these default values, we set group to None if 192 # it's 0 (also default value) which doesn't trigger the following chain 193 if group and group.name == "0": 194 group = None 195 196 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 197 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 198 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 199 200 return self.func( 201 "REGEXP_SUBSTR" if isinstance(expression, exp.RegexpExtract) else "REGEXP_SUBSTR_ALL", 202 expression.this, 203 expression.expression, 204 position, 205 occurrence, 206 parameters, 207 group, 208 ) 209 210 211def _json_extract_value_array_sql( 212 self: SnowflakeGenerator, expression: exp.JSONValueArray | exp.JSONExtractArray 213) -> str: 214 json_extract = exp.JSONExtract(this=expression.this, expression=expression.expression) 215 ident = exp.to_identifier("x") 216 217 if isinstance(expression, exp.JSONValueArray): 218 this: exp.Expr = exp.cast(ident, to=exp.DType.VARCHAR) 219 else: 220 this = exp.ParseJSON(this=f"TO_JSON({ident})") 221 222 transform_lambda = exp.Lambda(expressions=[ident], this=this) 223 224 return self.func("TRANSFORM", json_extract, transform_lambda) 225 226 227def _qualify_unnested_columns(expression: exp.Expr) -> exp.Expr: 228 if isinstance(expression, exp.Select): 229 scope = build_scope(expression) 230 if not scope: 231 return expression 232 233 unnests = list(scope.find_all(exp.Unnest)) 234 235 if not unnests: 236 return expression 237 238 taken_source_names = set(scope.sources) 239 column_source: dict[str, exp.Identifier] = {} 240 unnest_to_identifier: dict[exp.Unnest, exp.Identifier] = {} 241 242 unnest_identifier: exp.Identifier | None = None 243 orig_expression = expression.copy() 244 245 for unnest in unnests: 246 if not isinstance(unnest.parent, (exp.From, exp.Join)): 247 continue 248 249 # Try to infer column names produced by an unnest operator. This is only possible 250 # when we can peek into the (statically known) contents of the unnested value. 251 unnest_columns: set[str] = set() 252 for unnest_expr in unnest.expressions: 253 if not isinstance(unnest_expr, exp.Array): 254 continue 255 256 for array_expr in unnest_expr.expressions: 257 if not ( 258 isinstance(array_expr, exp.Struct) 259 and array_expr.expressions 260 and all( 261 isinstance(struct_expr, exp.PropertyEQ) 262 for struct_expr in array_expr.expressions 263 ) 264 ): 265 continue 266 267 unnest_columns.update( 268 struct_expr.this.name.lower() for struct_expr in array_expr.expressions 269 ) 270 break 271 272 if unnest_columns: 273 break 274 275 unnest_alias = unnest.args.get("alias") 276 if not unnest_alias: 277 alias_name = find_new_name(taken_source_names, "value") 278 taken_source_names.add(alias_name) 279 280 # Produce a `TableAlias` AST similar to what is produced for BigQuery. This 281 # will be corrected later, when we generate SQL for the `Unnest` AST node. 282 aliased_unnest = exp.alias_(unnest, None, table=[alias_name]) 283 scope.replace(unnest, aliased_unnest) 284 285 unnest_identifier = aliased_unnest.args["alias"].columns[0] 286 else: 287 alias_columns = getattr(unnest_alias, "columns", []) 288 unnest_identifier = unnest_alias.this or seq_get(alias_columns, 0) 289 290 if not isinstance(unnest_identifier, exp.Identifier): 291 return orig_expression 292 293 unnest_to_identifier[unnest] = unnest_identifier 294 column_source.update({c.lower(): unnest_identifier for c in unnest_columns}) 295 296 for column in scope.columns: 297 if column.table: 298 continue 299 300 table = column_source.get(column.name.lower()) 301 if ( 302 unnest_identifier 303 and not table 304 and len(scope.sources) == 1 305 and column.name.lower() != unnest_identifier.name.lower() 306 ): 307 unnest_ancestor = column.find_ancestor(exp.Unnest, exp.Select) 308 if isinstance(unnest_ancestor, exp.Unnest): 309 ancestor_identifier = unnest_to_identifier.get(unnest_ancestor) 310 if ( 311 ancestor_identifier 312 and ancestor_identifier.name.lower() == unnest_identifier.name.lower() 313 ): 314 continue 315 316 table = unnest_identifier 317 318 column.set("table", table and table.copy()) 319 320 return expression 321 322 323def _eliminate_dot_variant_lookup(expression: exp.Expr) -> exp.Expr: 324 if isinstance(expression, exp.Select): 325 # This transformation is used to facilitate transpilation of BigQuery `UNNEST` operations 326 # to Snowflake. It should not affect roundtrip because `Unnest` nodes cannot be produced 327 # by Snowflake's parser. 328 # 329 # Additionally, at the time of writing this, BigQuery is the only dialect that produces a 330 # `TableAlias` node that only fills `columns` and not `this`, due to `UNNEST_COLUMN_ONLY`. 331 unnest_aliases = set() 332 for unnest in find_all_in_scope(expression, exp.Unnest): 333 unnest_alias = unnest.args.get("alias") 334 if ( 335 isinstance(unnest_alias, exp.TableAlias) 336 and not unnest_alias.this 337 and len(unnest_alias.columns) == 1 338 ): 339 unnest_aliases.add(unnest_alias.columns[0].name) 340 341 if unnest_aliases: 342 for c in find_all_in_scope(expression, exp.Column): 343 if c.table in unnest_aliases: 344 bracket_lhs = c.args["table"] 345 bracket_rhs = exp.Literal.string(c.name) 346 bracket = exp.Bracket(this=bracket_lhs, expressions=[bracket_rhs]) 347 348 if c.parent is expression: 349 # Retain column projection names by using aliases 350 c.replace(exp.alias_(bracket, c.this.copy())) 351 else: 352 c.replace(bracket) 353 354 return expression 355 356 357class SnowflakeGenerator(generator.Generator): 358 SELECT_KINDS: tuple[str, ...] = () 359 PARAMETER_TOKEN = "$" 360 MATCHED_BY_SOURCE = False 361 SINGLE_STRING_INTERVAL = True 362 JOIN_HINTS = False 363 TABLE_HINTS = False 364 QUERY_HINTS = False 365 SUPPORTS_TABLE_COPY = False 366 COLLATE_IS_FUNC = True 367 LIMIT_ONLY_LITERALS = True 368 JSON_KEY_VALUE_PAIR_SEP = "," 369 INSERT_OVERWRITE = " OVERWRITE INTO" 370 STRUCT_DELIMITER = ("(", ")") 371 COPY_PARAMS_ARE_WRAPPED = False 372 COPY_PARAMS_EQ_REQUIRED = True 373 STAR_EXCEPT = "EXCLUDE" 374 SUPPORTS_EXPLODING_PROJECTIONS = False 375 ARRAY_CONCAT_IS_VAR_LEN = False 376 SUPPORTS_CONVERT_TIMEZONE = True 377 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 378 SUPPORTS_MEDIAN = True 379 ARRAY_SIZE_NAME = "ARRAY_SIZE" 380 SUPPORTS_DECODE_CASE = True 381 382 AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS 383 384 IS_BOOL_ALLOWED = False 385 DIRECTED_JOINS = True 386 SUPPORTS_UESCAPE = False 387 TRY_SUPPORTED = False 388 389 TRANSFORMS = { 390 **generator.Generator.TRANSFORMS, 391 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 392 exp.ArgMax: rename_func("MAX_BY"), 393 exp.ArgMin: rename_func("MIN_BY"), 394 exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]), 395 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 396 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 397 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"), 398 exp.ArrayContains: lambda self, e: self.func( 399 "ARRAY_CONTAINS", 400 e.expression 401 if e.args.get("ensure_variant") is False 402 else exp.cast(e.expression, exp.DType.VARIANT, copy=False), 403 e.this, 404 ), 405 exp.ArrayPosition: lambda self, e: self.func( 406 "ARRAY_POSITION", 407 e.expression, 408 e.this, 409 ), 410 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 411 exp.ArrayOverlaps: rename_func("ARRAYS_OVERLAP"), 412 exp.AtTimeZone: lambda self, e: self.func("CONVERT_TIMEZONE", e.args.get("zone"), e.this), 413 exp.BitwiseOr: rename_func("BITOR"), 414 exp.BitwiseXor: rename_func("BITXOR"), 415 exp.BitwiseAnd: rename_func("BITAND"), 416 exp.BitwiseAndAgg: rename_func("BITANDAGG"), 417 exp.BitwiseOrAgg: rename_func("BITORAGG"), 418 exp.BitwiseXorAgg: rename_func("BITXORAGG"), 419 exp.BitwiseNot: rename_func("BITNOT"), 420 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 421 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 422 exp.CurrentTimestamp: lambda self, e: ( 423 self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e) 424 ), 425 exp.CurrentSchemas: lambda self, e: self.func("CURRENT_SCHEMAS"), 426 exp.Localtime: lambda self, e: ( 427 self.func("CURRENT_TIME", e.this) if e.this else "CURRENT_TIME" 428 ), 429 exp.Localtimestamp: lambda self, e: ( 430 self.func("CURRENT_TIMESTAMP", e.this) if e.this else "CURRENT_TIMESTAMP" 431 ), 432 exp.DateAdd: date_delta_sql("DATEADD"), 433 exp.DateDiff: date_delta_sql("DATEDIFF"), 434 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 435 exp.DatetimeDiff: timestampdiff_sql, 436 exp.DateStrToDate: datestrtodate_sql, 437 exp.Decrypt: lambda self, e: self.func( 438 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT", 439 e.this, 440 e.args.get("passphrase"), 441 e.args.get("aad"), 442 e.args.get("encryption_method"), 443 ), 444 exp.DecryptRaw: lambda self, e: self.func( 445 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW", 446 e.this, 447 e.args.get("key"), 448 e.args.get("iv"), 449 e.args.get("aad"), 450 e.args.get("encryption_method"), 451 e.args.get("aead"), 452 ), 453 exp.DayOfMonth: rename_func("DAYOFMONTH"), 454 exp.DayOfWeek: rename_func("DAYOFWEEK"), 455 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 456 exp.DayOfYear: rename_func("DAYOFYEAR"), 457 exp.DotProduct: rename_func("VECTOR_INNER_PRODUCT"), 458 exp.Explode: rename_func("FLATTEN"), 459 exp.Extract: lambda self, e: self.func( 460 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 461 ), 462 exp.CosineDistance: rename_func("VECTOR_COSINE_SIMILARITY"), 463 exp.EuclideanDistance: rename_func("VECTOR_L2_DISTANCE"), 464 exp.HandlerProperty: lambda self, e: f"HANDLER = {self.sql(e, 'this')}", 465 exp.FileFormatProperty: lambda self, e: ( 466 f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})" 467 ), 468 exp.FromTimeZone: lambda self, e: self.func( 469 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 470 ), 471 exp.GenerateSeries: lambda self, e: self.func( 472 "ARRAY_GENERATE_RANGE", 473 e.args["start"], 474 e.args["end"] if e.args.get("is_end_exclusive") else e.args["end"] + 1, 475 e.args.get("step"), 476 ), 477 exp.GetExtract: rename_func("GET"), 478 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 479 exp.If: if_sql(name="IFF", false_value="NULL"), 480 exp.JSONArray: lambda self, e: self.func( 481 "TO_VARIANT", self.func("ARRAY_CONSTRUCT", *e.expressions) 482 ), 483 exp.JSONExtractArray: _json_extract_value_array_sql, 484 exp.JSONExtractScalar: lambda self, e: self.func( 485 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 486 ), 487 exp.JSONKeys: rename_func("OBJECT_KEYS"), 488 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 489 exp.JSONPathRoot: lambda *_: "", 490 exp.JSONValueArray: _json_extract_value_array_sql, 491 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 492 rename_func("EDITDISTANCE") 493 ), 494 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 495 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 496 exp.LogicalOr: rename_func("BOOLOR_AGG"), 497 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 498 exp.ManhattanDistance: rename_func("VECTOR_L1_DISTANCE"), 499 exp.MakeInterval: no_make_interval_sql, 500 exp.Max: max_or_greatest, 501 exp.Min: min_or_least, 502 exp.NthValue: nth_value_from_sql, 503 exp.ParseJSON: lambda self, e: self.func( 504 f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this 505 ), 506 exp.ToBinary: lambda self, e: self.func( 507 f"{'TRY_' if e.args.get('safe') else ''}TO_BINARY", e.this, e.args.get("format") 508 ), 509 exp.ToBoolean: lambda self, e: self.func( 510 f"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN", e.this 511 ), 512 exp.ToDouble: lambda self, e: self.func( 513 f"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE", e.this, e.args.get("format") 514 ), 515 exp.ToFile: lambda self, e: self.func( 516 f"{'TRY_' if e.args.get('safe') else ''}TO_FILE", e.this, e.args.get("path") 517 ), 518 exp.JSONFormat: rename_func("TO_JSON"), 519 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 520 exp.PercentileCont: transforms.preprocess([transforms.add_within_group_for_percentiles]), 521 exp.PercentileDisc: transforms.preprocess([transforms.add_within_group_for_percentiles]), 522 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 523 exp.RegexpExtract: _regexpextract_sql, 524 exp.RegexpExtractAll: _regexpextract_sql, 525 exp.RegexpILike: _regexpilike_sql, 526 exp.RowAccessProperty: lambda self, e: self.rowaccessproperty_sql(e), 527 exp.Select: transforms.preprocess( 528 [ 529 transforms.eliminate_window_clause, 530 transforms.eliminate_distinct_on, 531 transforms.explode_projection_to_unnest(), 532 transforms.eliminate_semi_and_anti_joins, 533 _transform_generate_date_array, 534 _qualify_unnested_columns, 535 _eliminate_dot_variant_lookup, 536 ] 537 ), 538 exp.SHA: rename_func("SHA1"), 539 exp.SHA1Digest: rename_func("SHA1_BINARY"), 540 exp.MD5Digest: rename_func("MD5_BINARY"), 541 exp.MD5NumberLower64: rename_func("MD5_NUMBER_LOWER64"), 542 exp.MD5NumberUpper64: rename_func("MD5_NUMBER_UPPER64"), 543 exp.Hex: rename_func("HEX_ENCODE"), 544 exp.LowerHex: rename_func("TO_CHAR"), 545 exp.Skewness: rename_func("SKEW"), 546 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 547 exp.StartsWith: rename_func("STARTSWITH"), 548 exp.EndsWith: rename_func("ENDSWITH"), 549 exp.Rand: lambda self, e: self.func("RANDOM", e.this), 550 exp.StrPosition: lambda self, e: strposition_sql( 551 self, e, func_name="CHARINDEX", supports_position=True 552 ), 553 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 554 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 555 exp.StrtokToArray: rename_func("STRTOK_TO_ARRAY"), 556 exp.Stuff: rename_func("INSERT"), 557 exp.StPoint: rename_func("ST_MAKEPOINT"), 558 exp.TimeAdd: date_delta_sql("TIMEADD"), 559 exp.TimeSlice: lambda self, e: self.func( 560 "TIME_SLICE", 561 e.this, 562 e.expression, 563 unit_to_str(e), 564 e.args.get("kind"), 565 ), 566 exp.Timestamp: no_timestamp_sql, 567 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 568 exp.TimestampDiff: lambda self, e: self.func("TIMESTAMPDIFF", e.unit, e.expression, e.this), 569 exp.TimestampTrunc: timestamptrunc_sql(), 570 exp.TimeStrToTime: timestrtotime_sql, 571 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 572 exp.ToArray: rename_func("TO_ARRAY"), 573 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 574 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 575 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 576 exp.TsOrDsToDate: lambda self, e: self.func( 577 f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e) 578 ), 579 exp.TsOrDsToTime: lambda self, e: self.func( 580 f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e) 581 ), 582 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 583 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, e.args.get("scale")), 584 exp.Uuid: rename_func("UUID_STRING"), 585 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 586 exp.Booland: rename_func("BOOLAND"), 587 exp.Boolor: rename_func("BOOLOR"), 588 exp.WeekOfYear: rename_func("WEEKISO"), 589 exp.YearOfWeek: rename_func("YEAROFWEEK"), 590 exp.YearOfWeekIso: rename_func("YEAROFWEEKISO"), 591 exp.Xor: rename_func("BOOLXOR"), 592 exp.ByteLength: rename_func("OCTET_LENGTH"), 593 exp.Flatten: rename_func("ARRAY_FLATTEN"), 594 exp.ArrayConcatAgg: lambda self, e: self.func("ARRAY_FLATTEN", exp.ArrayAgg(this=e.this)), 595 exp.SHA2Digest: lambda self, e: self.func( 596 "SHA2_BINARY", e.this, e.args.get("length") or exp.Literal.number(256) 597 ), 598 } 599 600 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 601 this = self.func("IDENTIFIER", expression.this) 602 if "expressions" in expression.args: 603 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 604 return self.func(this, *expression.expressions, normalize=False) 605 return this 606 607 def sortarray_sql(self, expression: exp.SortArray) -> str: 608 asc = expression.args.get("asc") 609 nulls_first = expression.args.get("nulls_first") 610 if asc == exp.false() and nulls_first == exp.true(): 611 nulls_first = None 612 return self.func("ARRAY_SORT", expression.this, asc, nulls_first) 613 614 SUPPORTED_JSON_PATH_PARTS = { 615 exp.JSONPathKey, 616 exp.JSONPathRoot, 617 exp.JSONPathSubscript, 618 } 619 620 TYPE_MAPPING = { 621 **generator.Generator.TYPE_MAPPING, 622 exp.DType.BIGDECIMAL: "DOUBLE", 623 exp.DType.JSON: "VARIANT", 624 exp.DType.NESTED: "OBJECT", 625 exp.DType.STRUCT: "OBJECT", 626 exp.DType.TEXT: "VARCHAR", 627 } 628 629 TOKEN_MAPPING = { 630 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 631 } 632 633 PROPERTIES_LOCATION = { 634 **generator.Generator.PROPERTIES_LOCATION, 635 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 636 exp.LocationProperty: exp.Properties.Location.POST_WITH, 637 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.RowAccessProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 640 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 641 } 642 643 UNSUPPORTED_VALUES_EXPRESSIONS: t.ClassVar = { 644 exp.Map, 645 exp.StarMap, 646 exp.Struct, 647 exp.VarMap, 648 } 649 650 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 651 652 def with_properties(self, properties: exp.Properties) -> str: 653 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 654 655 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 656 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 657 values_as_table = False 658 659 return super().values_sql(expression, values_as_table=values_as_table) 660 661 def datatype_sql(self, expression: exp.DataType) -> str: 662 # Check if this is a FLOAT type nested inside a VECTOR type 663 # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types 664 # https://docs.snowflake.com/en/sql-reference/data-types-vector 665 if expression.is_type(exp.DType.DOUBLE): 666 parent = expression.parent 667 if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR): 668 # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE 669 return "FLOAT" 670 671 expressions = expression.expressions 672 if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES): 673 for field_type in expressions: 674 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 675 if isinstance(field_type, exp.DataType): 676 return "OBJECT" 677 if ( 678 isinstance(field_type, exp.ColumnDef) 679 and field_type.this 680 and field_type.this.is_string 681 ): 682 # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides 683 # converting 'foo' into an identifier, we also need to quote it because these 684 # keys are case-sensitive. For example: 685 # 686 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct 687 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL 688 field_type.this.replace(exp.to_identifier(field_type.name, quoted=True)) 689 690 return super().datatype_sql(expression) 691 692 def tonumber_sql(self, expression: exp.ToNumber) -> str: 693 precision = expression.args.get("precision") 694 scale = expression.args.get("scale") 695 696 default_precision = isinstance(precision, exp.Literal) and precision.name == "38" 697 default_scale = isinstance(scale, exp.Literal) and scale.name == "0" 698 699 if default_precision and default_scale: 700 precision = None 701 scale = None 702 elif default_scale: 703 scale = None 704 705 func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER" 706 707 return self.func( 708 func_name, 709 expression.this, 710 expression.args.get("format"), 711 precision, 712 scale, 713 ) 714 715 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 716 milli = expression.args.get("milli") 717 if milli is not None: 718 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 719 expression.set("nano", milli_to_nano) 720 721 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 722 723 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 724 if expression.is_type(exp.DType.GEOGRAPHY): 725 return self.func("TO_GEOGRAPHY", expression.this) 726 if expression.is_type(exp.DType.GEOMETRY): 727 return self.func("TO_GEOMETRY", expression.this) 728 729 return super().cast_sql(expression, safe_prefix=safe_prefix) 730 731 def trycast_sql(self, expression: exp.TryCast) -> str: 732 value = expression.this 733 734 if value.type is None: 735 from sqlglot.optimizer.annotate_types import annotate_types 736 737 value = annotate_types(value, dialect=self.dialect) 738 739 # Snowflake requires that TRY_CAST's value be a string 740 # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or 741 # if we can deduce that the value is a string, then we can generate TRY_CAST 742 if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES): 743 return super().trycast_sql(expression) 744 745 return self.cast_sql(expression) 746 747 def log_sql(self, expression: exp.Log) -> str: 748 if not expression.expression: 749 return self.func("LN", expression.this) 750 751 return super().log_sql(expression) 752 753 def greatest_sql(self, expression: exp.Greatest) -> str: 754 name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST" 755 return self.func(name, expression.this, *expression.expressions) 756 757 def least_sql(self, expression: exp.Least) -> str: 758 name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST" 759 return self.func(name, expression.this, *expression.expressions) 760 761 def generator_sql(self, expression: exp.Generator) -> str: 762 args = [] 763 rowcount = expression.args.get("rowcount") 764 timelimit = expression.args.get("timelimit") 765 766 if rowcount: 767 args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount)) 768 if timelimit: 769 args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit)) 770 771 return self.func("GENERATOR", *args) 772 773 def unnest_sql(self, expression: exp.Unnest) -> str: 774 unnest_alias = expression.args.get("alias") 775 offset = expression.args.get("offset") 776 777 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 778 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 779 780 columns = [ 781 exp.to_identifier("seq"), 782 exp.to_identifier("key"), 783 exp.to_identifier("path"), 784 offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"), 785 value, 786 exp.to_identifier("this"), 787 ] 788 789 if unnest_alias: 790 unnest_alias.set("columns", columns) 791 else: 792 unnest_alias = exp.TableAlias(this="_u", columns=columns) 793 794 table_input = self.sql(expression.expressions[0]) 795 if not table_input.startswith("INPUT =>"): 796 table_input = f"INPUT => {table_input}" 797 798 expression_parent = expression.parent 799 800 explode = ( 801 f"FLATTEN({table_input})" 802 if isinstance(expression_parent, exp.Lateral) 803 else f"TABLE(FLATTEN({table_input}))" 804 ) 805 alias = self.sql(unnest_alias) 806 alias = f" AS {alias}" if alias else "" 807 value = ( 808 "" 809 if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral)) 810 else f"{value} FROM " 811 ) 812 813 return f"{value}{explode}{alias}" 814 815 def undrop_sql(self, expression: exp.Undrop) -> str: 816 this = self.sql(expression, "this") 817 kind = expression.kind 818 rename = self.sql(expression, "rename") 819 rename = f" RENAME TO {rename}" if rename else "" 820 return f"UNDROP {kind} {this}{rename}" 821 822 def show_sql(self, expression: exp.Show) -> str: 823 terse = "TERSE " if expression.args.get("terse") else "" 824 iceberg = "ICEBERG " if expression.args.get("iceberg") else "" 825 history = " HISTORY" if expression.args.get("history") else "" 826 like = self.sql(expression, "like") 827 like = f" LIKE {like}" if like else "" 828 829 scope = self.sql(expression, "scope") 830 scope = f" {scope}" if scope else "" 831 832 scope_kind = self.sql(expression, "scope_kind") 833 if scope_kind: 834 scope_kind = f" IN {scope_kind}" 835 836 starts_with = self.sql(expression, "starts_with") 837 if starts_with: 838 starts_with = f" STARTS WITH {starts_with}" 839 840 limit = self.sql(expression, "limit") 841 842 from_ = self.sql(expression, "from_") 843 if from_: 844 from_ = f" FROM {from_}" 845 846 privileges = self.expressions(expression, key="privileges", flat=True) 847 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 848 849 return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 850 851 def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str: 852 if not expression.this: 853 return "ROW ACCESS" 854 on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else "" 855 return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}" 856 857 def describe_sql(self, expression: exp.Describe) -> str: 858 kind_value = expression.args.get("kind") or "TABLE" 859 860 properties = expression.args.get("properties") 861 if properties: 862 qualifier = self.expressions(properties, sep=" ") 863 kind = f" {qualifier} {kind_value}" 864 else: 865 kind = f" {kind_value}" 866 867 this = f" {self.sql(expression, 'this')}" 868 expressions = self.expressions(expression, flat=True) 869 expressions = f" {expressions}" if expressions else "" 870 return f"DESCRIBE{kind}{this}{expressions}" 871 872 def generatedasidentitycolumnconstraint_sql( 873 self, expression: exp.GeneratedAsIdentityColumnConstraint 874 ) -> str: 875 start = expression.args.get("start") 876 start = f" START {start}" if start else "" 877 increment = expression.args.get("increment") 878 increment = f" INCREMENT {increment}" if increment else "" 879 880 order = expression.args.get("order") 881 if order is not None: 882 order_clause = " ORDER" if order else " NOORDER" 883 else: 884 order_clause = "" 885 886 return f"AUTOINCREMENT{start}{increment}{order_clause}" 887 888 def struct_sql(self, expression: exp.Struct) -> str: 889 if len(expression.expressions) == 1: 890 arg = expression.expressions[0] 891 if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star): 892 # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object 893 return f"{{{self.sql(expression.expressions[0])}}}" 894 895 keys = [] 896 values = [] 897 898 for i, e in enumerate(expression.expressions): 899 if isinstance(e, exp.PropertyEQ): 900 keys.append( 901 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 902 ) 903 values.append(e.expression) 904 else: 905 keys.append(exp.Literal.string(f"_{i}")) 906 values.append(e) 907 908 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 909 910 @unsupported_args("weight", "accuracy") 911 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 912 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 913 914 def alterset_sql(self, expression: exp.AlterSet) -> str: 915 exprs = self.expressions(expression, flat=True) 916 exprs = f" {exprs}" if exprs else "" 917 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 918 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 919 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 920 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 921 tag = self.expressions(expression, key="tag", flat=True) 922 tag = f" TAG {tag}" if tag else "" 923 924 return f"SET{exprs}{file_format}{copy_options}{tag}" 925 926 def strtotime_sql(self, expression: exp.StrToTime): 927 # target_type is stored as a DataType instance 928 target_type = expression.args.get("target_type") 929 930 # Get the type enum from DataType instance or from type annotation 931 if isinstance(target_type, exp.DataType): 932 type_enum = target_type.this 933 elif expression.type: 934 type_enum = expression.type.this 935 else: 936 type_enum = exp.DType.TIMESTAMP 937 938 func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP") 939 940 return self.func( 941 f"{'TRY_' if expression.args.get('safe') else ''}{func_name}", 942 expression.this, 943 self.format_time(expression), 944 ) 945 946 def timestampsub_sql(self, expression: exp.TimestampSub): 947 return self.sql( 948 exp.TimestampAdd( 949 this=expression.this, 950 expression=expression.expression * -1, 951 unit=expression.unit, 952 ) 953 ) 954 955 def jsonextract_sql(self, expression: exp.JSONExtract): 956 this = expression.this 957 958 # JSON strings are valid coming from other dialects such as BQ so 959 # for these cases we PARSE_JSON preemptively 960 if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get( 961 "requires_json" 962 ): 963 this = exp.ParseJSON(this=this) 964 965 return self.func( 966 "GET_PATH", 967 this, 968 expression.expression, 969 ) 970 971 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 972 this = expression.this 973 if this.is_string: 974 this = exp.cast(this, exp.DType.TIMESTAMP) 975 976 return self.func("TO_CHAR", this, self.format_time(expression)) 977 978 def datesub_sql(self, expression: exp.DateSub) -> str: 979 value = expression.expression 980 if value: 981 value.replace(value * (-1)) 982 else: 983 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 984 985 return date_delta_sql("DATEADD")(self, expression) 986 987 def select_sql(self, expression: exp.Select) -> str: 988 limit = expression.args.get("limit") 989 offset = expression.args.get("offset") 990 if offset and not limit: 991 expression.limit(exp.Null(), copy=False) 992 return super().select_sql(expression) 993 994 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 995 is_materialized = expression.find(exp.MaterializedProperty) 996 copy_grants_property = expression.find(exp.CopyGrantsProperty) 997 998 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 999 # For materialized views, COPY GRANTS is located *before* the columns list 1000 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1001 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1002 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1003 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1004 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1005 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1006 1007 this_name = self.sql(expression.this, "this") 1008 copy_grants = self.sql(copy_grants_property) 1009 this_schema = self.schema_columns_sql(expression.this) 1010 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1011 1012 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1013 1014 return super().createable_sql(expression, locations) 1015 1016 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1017 this = expression.this 1018 1019 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1020 # and add it later as part of the WITHIN GROUP clause 1021 order = this if isinstance(this, exp.Order) else None 1022 if order: 1023 expression.set("this", order.this.pop()) 1024 1025 expr_sql = super().arrayagg_sql(expression) 1026 1027 if order: 1028 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1029 1030 return expr_sql 1031 1032 def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str: 1033 if expression.args.get("check_null"): 1034 return self.func("ARRAY_DISTINCT", expression.this) 1035 return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this)) 1036 1037 def arraytostring_sql(self, expression: exp.ArrayToString) -> str: 1038 return self.func("ARRAY_TO_STRING", expression.this, expression.expression) 1039 1040 def array_sql(self, expression: exp.Array) -> str: 1041 expressions = expression.expressions 1042 1043 first_expr = seq_get(expressions, 0) 1044 if isinstance(first_expr, exp.Select): 1045 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1046 if first_expr.text("kind").upper() == "STRUCT": 1047 object_construct_args = [] 1048 for expr in first_expr.expressions: 1049 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1050 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1051 name = expr.this if isinstance(expr, exp.Alias) else expr 1052 1053 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1054 1055 array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args)) 1056 1057 first_expr.set("kind", None) 1058 first_expr.set("expressions", [array_agg]) 1059 1060 return self.sql(first_expr.subquery()) 1061 1062 return inline_array_sql(self, expression) 1063 1064 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1065 zone = self.sql(expression, "this") 1066 if not zone: 1067 return super().currentdate_sql(expression) 1068 1069 expr = exp.Cast( 1070 this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()), 1071 to=exp.DataType(this=exp.DType.DATE), 1072 ) 1073 return self.sql(expr) 1074 1075 def dot_sql(self, expression: exp.Dot) -> str: 1076 this = expression.this 1077 1078 if not this.type: 1079 from sqlglot.optimizer.annotate_types import annotate_types 1080 1081 this = annotate_types(this, dialect=self.dialect) 1082 1083 if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT): 1084 # Generate colon notation for the top level STRUCT 1085 return f"{self.sql(this)}:{self.sql(expression, 'expression')}" 1086 1087 return super().dot_sql(expression) 1088 1089 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 1090 return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}" 1091 1092 def format_sql(self, expression: exp.Format) -> str: 1093 if expression.name.lower() == "%s" and len(expression.expressions) == 1: 1094 return self.func("TO_CHAR", expression.expressions[0]) 1095 1096 return self.function_fallback_sql(expression) 1097 1098 def splitpart_sql(self, expression: exp.SplitPart) -> str: 1099 # Set part_index to 1 if missing 1100 if not expression.args.get("delimiter"): 1101 expression.set("delimiter", exp.Literal.string(" ")) 1102 1103 if not expression.args.get("part_index"): 1104 expression.set("part_index", exp.Literal.number(1)) 1105 1106 return rename_func("SPLIT_PART")(self, expression) 1107 1108 def uniform_sql(self, expression: exp.Uniform) -> str: 1109 gen = expression.args.get("gen") 1110 seed = expression.args.get("seed") 1111 1112 # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed) 1113 if seed: 1114 gen = exp.Rand(this=seed) 1115 1116 # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM() 1117 if not gen: 1118 gen = exp.Rand() 1119 1120 return self.func("UNIFORM", expression.this, expression.expression, gen) 1121 1122 def window_sql(self, expression: exp.Window) -> str: 1123 spec = expression.args.get("spec") 1124 this = expression.this 1125 1126 if ( 1127 ( 1128 isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1129 or ( 1130 isinstance(this, (exp.RespectNulls, exp.IgnoreNulls)) 1131 and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1132 ) 1133 ) 1134 and spec 1135 and ( 1136 spec.text("kind").upper() == "ROWS" 1137 and spec.text("start").upper() == "UNBOUNDED" 1138 and spec.text("start_side").upper() == "PRECEDING" 1139 and spec.text("end").upper() == "UNBOUNDED" 1140 and spec.text("end_side").upper() == "FOLLOWING" 1141 ) 1142 ): 1143 # omit the default window from window ranking functions 1144 expression.set("spec", None) 1145 return super().window_sql(expression) 1146 1147 def filter_sql(self, expression: exp.Filter) -> str: 1148 # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an 1149 # equivalent conditional aggregation, i.e. wrap the input values in an IFF 1150 agg = expression.this 1151 agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this 1152 cond = expression.expression.this 1153 1154 if isinstance(agg, exp.WithinGroup): 1155 # Ordered-set aggregates take their input from the ORDER BY key, so the 1156 # condition has to wrap that instead of the aggregate's own argument 1157 if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)): 1158 for ordered in agg.expression.expressions: 1159 key = ordered.this 1160 key.replace(exp.If(this=cond.copy(), true=key.copy())) 1161 1162 return self.sql(agg) 1163 1164 # Besides the percentile functions, these are the only functions Snowflake 1165 # accepts WITHIN GROUP for, so anything else can't be rewritten correctly 1166 if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)): 1167 agg_arg = agg_arg.this 1168 else: 1169 self.unsupported("Unable to rewrite FILTER into the aggregate's arguments") 1170 return self.sql(agg) 1171 1172 # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF 1173 # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of 1174 # them, which Snowflake rejects. Use its native COUNT_IF instead. 1175 if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star: 1176 return self.func("COUNT_IF", cond) 1177 1178 # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the 1179 # condition has to wrap the values underneath them rather than the whole clause -- 1180 # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts. 1181 if isinstance(agg_arg, exp.Order): 1182 agg_arg = agg_arg.this 1183 1184 if isinstance(agg_arg, exp.Distinct): 1185 targets = agg_arg.expressions 1186 else: 1187 targets = [agg_arg] 1188 1189 for target in targets: 1190 target.replace(exp.If(this=cond.copy(), true=target.copy())) 1191 1192 return self.sql(agg) 1193 1194 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1195 # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only 1196 # accepts the value to aggregate as an argument: MODE(<expr>) 1197 if isinstance(expression.this, exp.Mode) and not expression.this.this: 1198 order = expression.expression 1199 if isinstance(order, exp.Order) and len(order.expressions) == 1: 1200 return self.sql(exp.Mode(this=order.expressions[0].this)) 1201 1202 return super().withingroup_sql(expression)
358class SnowflakeGenerator(generator.Generator): 359 SELECT_KINDS: tuple[str, ...] = () 360 PARAMETER_TOKEN = "$" 361 MATCHED_BY_SOURCE = False 362 SINGLE_STRING_INTERVAL = True 363 JOIN_HINTS = False 364 TABLE_HINTS = False 365 QUERY_HINTS = False 366 SUPPORTS_TABLE_COPY = False 367 COLLATE_IS_FUNC = True 368 LIMIT_ONLY_LITERALS = True 369 JSON_KEY_VALUE_PAIR_SEP = "," 370 INSERT_OVERWRITE = " OVERWRITE INTO" 371 STRUCT_DELIMITER = ("(", ")") 372 COPY_PARAMS_ARE_WRAPPED = False 373 COPY_PARAMS_EQ_REQUIRED = True 374 STAR_EXCEPT = "EXCLUDE" 375 SUPPORTS_EXPLODING_PROJECTIONS = False 376 ARRAY_CONCAT_IS_VAR_LEN = False 377 SUPPORTS_CONVERT_TIMEZONE = True 378 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False 379 SUPPORTS_MEDIAN = True 380 ARRAY_SIZE_NAME = "ARRAY_SIZE" 381 SUPPORTS_DECODE_CASE = True 382 383 AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS 384 385 IS_BOOL_ALLOWED = False 386 DIRECTED_JOINS = True 387 SUPPORTS_UESCAPE = False 388 TRY_SUPPORTED = False 389 390 TRANSFORMS = { 391 **generator.Generator.TRANSFORMS, 392 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), 393 exp.ArgMax: rename_func("MAX_BY"), 394 exp.ArgMin: rename_func("MIN_BY"), 395 exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]), 396 exp.ArrayConcat: array_concat_sql("ARRAY_CAT"), 397 exp.ArrayAppend: array_append_sql("ARRAY_APPEND"), 398 exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"), 399 exp.ArrayContains: lambda self, e: self.func( 400 "ARRAY_CONTAINS", 401 e.expression 402 if e.args.get("ensure_variant") is False 403 else exp.cast(e.expression, exp.DType.VARIANT, copy=False), 404 e.this, 405 ), 406 exp.ArrayPosition: lambda self, e: self.func( 407 "ARRAY_POSITION", 408 e.expression, 409 e.this, 410 ), 411 exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"), 412 exp.ArrayOverlaps: rename_func("ARRAYS_OVERLAP"), 413 exp.AtTimeZone: lambda self, e: self.func("CONVERT_TIMEZONE", e.args.get("zone"), e.this), 414 exp.BitwiseOr: rename_func("BITOR"), 415 exp.BitwiseXor: rename_func("BITXOR"), 416 exp.BitwiseAnd: rename_func("BITAND"), 417 exp.BitwiseAndAgg: rename_func("BITANDAGG"), 418 exp.BitwiseOrAgg: rename_func("BITORAGG"), 419 exp.BitwiseXorAgg: rename_func("BITXORAGG"), 420 exp.BitwiseNot: rename_func("BITNOT"), 421 exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"), 422 exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"), 423 exp.CurrentTimestamp: lambda self, e: ( 424 self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e) 425 ), 426 exp.CurrentSchemas: lambda self, e: self.func("CURRENT_SCHEMAS"), 427 exp.Localtime: lambda self, e: ( 428 self.func("CURRENT_TIME", e.this) if e.this else "CURRENT_TIME" 429 ), 430 exp.Localtimestamp: lambda self, e: ( 431 self.func("CURRENT_TIMESTAMP", e.this) if e.this else "CURRENT_TIMESTAMP" 432 ), 433 exp.DateAdd: date_delta_sql("DATEADD"), 434 exp.DateDiff: date_delta_sql("DATEDIFF"), 435 exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"), 436 exp.DatetimeDiff: timestampdiff_sql, 437 exp.DateStrToDate: datestrtodate_sql, 438 exp.Decrypt: lambda self, e: self.func( 439 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT", 440 e.this, 441 e.args.get("passphrase"), 442 e.args.get("aad"), 443 e.args.get("encryption_method"), 444 ), 445 exp.DecryptRaw: lambda self, e: self.func( 446 f"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW", 447 e.this, 448 e.args.get("key"), 449 e.args.get("iv"), 450 e.args.get("aad"), 451 e.args.get("encryption_method"), 452 e.args.get("aead"), 453 ), 454 exp.DayOfMonth: rename_func("DAYOFMONTH"), 455 exp.DayOfWeek: rename_func("DAYOFWEEK"), 456 exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"), 457 exp.DayOfYear: rename_func("DAYOFYEAR"), 458 exp.DotProduct: rename_func("VECTOR_INNER_PRODUCT"), 459 exp.Explode: rename_func("FLATTEN"), 460 exp.Extract: lambda self, e: self.func( 461 "DATE_PART", map_date_part(e.this, self.dialect), e.expression 462 ), 463 exp.CosineDistance: rename_func("VECTOR_COSINE_SIMILARITY"), 464 exp.EuclideanDistance: rename_func("VECTOR_L2_DISTANCE"), 465 exp.HandlerProperty: lambda self, e: f"HANDLER = {self.sql(e, 'this')}", 466 exp.FileFormatProperty: lambda self, e: ( 467 f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})" 468 ), 469 exp.FromTimeZone: lambda self, e: self.func( 470 "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this 471 ), 472 exp.GenerateSeries: lambda self, e: self.func( 473 "ARRAY_GENERATE_RANGE", 474 e.args["start"], 475 e.args["end"] if e.args.get("is_end_exclusive") else e.args["end"] + 1, 476 e.args.get("step"), 477 ), 478 exp.GetExtract: rename_func("GET"), 479 exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""), 480 exp.If: if_sql(name="IFF", false_value="NULL"), 481 exp.JSONArray: lambda self, e: self.func( 482 "TO_VARIANT", self.func("ARRAY_CONSTRUCT", *e.expressions) 483 ), 484 exp.JSONExtractArray: _json_extract_value_array_sql, 485 exp.JSONExtractScalar: lambda self, e: self.func( 486 "JSON_EXTRACT_PATH_TEXT", e.this, e.expression 487 ), 488 exp.JSONKeys: rename_func("OBJECT_KEYS"), 489 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 490 exp.JSONPathRoot: lambda *_: "", 491 exp.JSONValueArray: _json_extract_value_array_sql, 492 exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")( 493 rename_func("EDITDISTANCE") 494 ), 495 exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}", 496 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 497 exp.LogicalOr: rename_func("BOOLOR_AGG"), 498 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 499 exp.ManhattanDistance: rename_func("VECTOR_L1_DISTANCE"), 500 exp.MakeInterval: no_make_interval_sql, 501 exp.Max: max_or_greatest, 502 exp.Min: min_or_least, 503 exp.NthValue: nth_value_from_sql, 504 exp.ParseJSON: lambda self, e: self.func( 505 f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this 506 ), 507 exp.ToBinary: lambda self, e: self.func( 508 f"{'TRY_' if e.args.get('safe') else ''}TO_BINARY", e.this, e.args.get("format") 509 ), 510 exp.ToBoolean: lambda self, e: self.func( 511 f"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN", e.this 512 ), 513 exp.ToDouble: lambda self, e: self.func( 514 f"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE", e.this, e.args.get("format") 515 ), 516 exp.ToFile: lambda self, e: self.func( 517 f"{'TRY_' if e.args.get('safe') else ''}TO_FILE", e.this, e.args.get("path") 518 ), 519 exp.JSONFormat: rename_func("TO_JSON"), 520 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 521 exp.PercentileCont: transforms.preprocess([transforms.add_within_group_for_percentiles]), 522 exp.PercentileDisc: transforms.preprocess([transforms.add_within_group_for_percentiles]), 523 exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]), 524 exp.RegexpExtract: _regexpextract_sql, 525 exp.RegexpExtractAll: _regexpextract_sql, 526 exp.RegexpILike: _regexpilike_sql, 527 exp.RowAccessProperty: lambda self, e: self.rowaccessproperty_sql(e), 528 exp.Select: transforms.preprocess( 529 [ 530 transforms.eliminate_window_clause, 531 transforms.eliminate_distinct_on, 532 transforms.explode_projection_to_unnest(), 533 transforms.eliminate_semi_and_anti_joins, 534 _transform_generate_date_array, 535 _qualify_unnested_columns, 536 _eliminate_dot_variant_lookup, 537 ] 538 ), 539 exp.SHA: rename_func("SHA1"), 540 exp.SHA1Digest: rename_func("SHA1_BINARY"), 541 exp.MD5Digest: rename_func("MD5_BINARY"), 542 exp.MD5NumberLower64: rename_func("MD5_NUMBER_LOWER64"), 543 exp.MD5NumberUpper64: rename_func("MD5_NUMBER_UPPER64"), 544 exp.Hex: rename_func("HEX_ENCODE"), 545 exp.LowerHex: rename_func("TO_CHAR"), 546 exp.Skewness: rename_func("SKEW"), 547 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 548 exp.StartsWith: rename_func("STARTSWITH"), 549 exp.EndsWith: rename_func("ENDSWITH"), 550 exp.Rand: lambda self, e: self.func("RANDOM", e.this), 551 exp.StrPosition: lambda self, e: strposition_sql( 552 self, e, func_name="CHARINDEX", supports_position=True 553 ), 554 exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)), 555 exp.StringToArray: rename_func("STRTOK_TO_ARRAY"), 556 exp.StrtokToArray: rename_func("STRTOK_TO_ARRAY"), 557 exp.Stuff: rename_func("INSERT"), 558 exp.StPoint: rename_func("ST_MAKEPOINT"), 559 exp.TimeAdd: date_delta_sql("TIMEADD"), 560 exp.TimeSlice: lambda self, e: self.func( 561 "TIME_SLICE", 562 e.this, 563 e.expression, 564 unit_to_str(e), 565 e.args.get("kind"), 566 ), 567 exp.Timestamp: no_timestamp_sql, 568 exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"), 569 exp.TimestampDiff: lambda self, e: self.func("TIMESTAMPDIFF", e.unit, e.expression, e.this), 570 exp.TimestampTrunc: timestamptrunc_sql(), 571 exp.TimeStrToTime: timestrtotime_sql, 572 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 573 exp.ToArray: rename_func("TO_ARRAY"), 574 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 575 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 576 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 577 exp.TsOrDsToDate: lambda self, e: self.func( 578 f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e) 579 ), 580 exp.TsOrDsToTime: lambda self, e: self.func( 581 f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e) 582 ), 583 exp.Unhex: rename_func("HEX_DECODE_BINARY"), 584 exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, e.args.get("scale")), 585 exp.Uuid: rename_func("UUID_STRING"), 586 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 587 exp.Booland: rename_func("BOOLAND"), 588 exp.Boolor: rename_func("BOOLOR"), 589 exp.WeekOfYear: rename_func("WEEKISO"), 590 exp.YearOfWeek: rename_func("YEAROFWEEK"), 591 exp.YearOfWeekIso: rename_func("YEAROFWEEKISO"), 592 exp.Xor: rename_func("BOOLXOR"), 593 exp.ByteLength: rename_func("OCTET_LENGTH"), 594 exp.Flatten: rename_func("ARRAY_FLATTEN"), 595 exp.ArrayConcatAgg: lambda self, e: self.func("ARRAY_FLATTEN", exp.ArrayAgg(this=e.this)), 596 exp.SHA2Digest: lambda self, e: self.func( 597 "SHA2_BINARY", e.this, e.args.get("length") or exp.Literal.number(256) 598 ), 599 } 600 601 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 602 this = self.func("IDENTIFIER", expression.this) 603 if "expressions" in expression.args: 604 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 605 return self.func(this, *expression.expressions, normalize=False) 606 return this 607 608 def sortarray_sql(self, expression: exp.SortArray) -> str: 609 asc = expression.args.get("asc") 610 nulls_first = expression.args.get("nulls_first") 611 if asc == exp.false() and nulls_first == exp.true(): 612 nulls_first = None 613 return self.func("ARRAY_SORT", expression.this, asc, nulls_first) 614 615 SUPPORTED_JSON_PATH_PARTS = { 616 exp.JSONPathKey, 617 exp.JSONPathRoot, 618 exp.JSONPathSubscript, 619 } 620 621 TYPE_MAPPING = { 622 **generator.Generator.TYPE_MAPPING, 623 exp.DType.BIGDECIMAL: "DOUBLE", 624 exp.DType.JSON: "VARIANT", 625 exp.DType.NESTED: "OBJECT", 626 exp.DType.STRUCT: "OBJECT", 627 exp.DType.TEXT: "VARCHAR", 628 } 629 630 TOKEN_MAPPING = { 631 TokenType.AUTO_INCREMENT: "AUTOINCREMENT", 632 } 633 634 PROPERTIES_LOCATION = { 635 **generator.Generator.PROPERTIES_LOCATION, 636 exp.CredentialsProperty: exp.Properties.Location.POST_WITH, 637 exp.LocationProperty: exp.Properties.Location.POST_WITH, 638 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.RowAccessProperty: exp.Properties.Location.POST_SCHEMA, 640 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 641 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 642 } 643 644 UNSUPPORTED_VALUES_EXPRESSIONS: t.ClassVar = { 645 exp.Map, 646 exp.StarMap, 647 exp.Struct, 648 exp.VarMap, 649 } 650 651 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,) 652 653 def with_properties(self, properties: exp.Properties) -> str: 654 return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ") 655 656 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 657 if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS): 658 values_as_table = False 659 660 return super().values_sql(expression, values_as_table=values_as_table) 661 662 def datatype_sql(self, expression: exp.DataType) -> str: 663 # Check if this is a FLOAT type nested inside a VECTOR type 664 # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types 665 # https://docs.snowflake.com/en/sql-reference/data-types-vector 666 if expression.is_type(exp.DType.DOUBLE): 667 parent = expression.parent 668 if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR): 669 # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE 670 return "FLOAT" 671 672 expressions = expression.expressions 673 if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES): 674 for field_type in expressions: 675 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 676 if isinstance(field_type, exp.DataType): 677 return "OBJECT" 678 if ( 679 isinstance(field_type, exp.ColumnDef) 680 and field_type.this 681 and field_type.this.is_string 682 ): 683 # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides 684 # converting 'foo' into an identifier, we also need to quote it because these 685 # keys are case-sensitive. For example: 686 # 687 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct 688 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL 689 field_type.this.replace(exp.to_identifier(field_type.name, quoted=True)) 690 691 return super().datatype_sql(expression) 692 693 def tonumber_sql(self, expression: exp.ToNumber) -> str: 694 precision = expression.args.get("precision") 695 scale = expression.args.get("scale") 696 697 default_precision = isinstance(precision, exp.Literal) and precision.name == "38" 698 default_scale = isinstance(scale, exp.Literal) and scale.name == "0" 699 700 if default_precision and default_scale: 701 precision = None 702 scale = None 703 elif default_scale: 704 scale = None 705 706 func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER" 707 708 return self.func( 709 func_name, 710 expression.this, 711 expression.args.get("format"), 712 precision, 713 scale, 714 ) 715 716 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 717 milli = expression.args.get("milli") 718 if milli is not None: 719 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 720 expression.set("nano", milli_to_nano) 721 722 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 723 724 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 725 if expression.is_type(exp.DType.GEOGRAPHY): 726 return self.func("TO_GEOGRAPHY", expression.this) 727 if expression.is_type(exp.DType.GEOMETRY): 728 return self.func("TO_GEOMETRY", expression.this) 729 730 return super().cast_sql(expression, safe_prefix=safe_prefix) 731 732 def trycast_sql(self, expression: exp.TryCast) -> str: 733 value = expression.this 734 735 if value.type is None: 736 from sqlglot.optimizer.annotate_types import annotate_types 737 738 value = annotate_types(value, dialect=self.dialect) 739 740 # Snowflake requires that TRY_CAST's value be a string 741 # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or 742 # if we can deduce that the value is a string, then we can generate TRY_CAST 743 if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES): 744 return super().trycast_sql(expression) 745 746 return self.cast_sql(expression) 747 748 def log_sql(self, expression: exp.Log) -> str: 749 if not expression.expression: 750 return self.func("LN", expression.this) 751 752 return super().log_sql(expression) 753 754 def greatest_sql(self, expression: exp.Greatest) -> str: 755 name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST" 756 return self.func(name, expression.this, *expression.expressions) 757 758 def least_sql(self, expression: exp.Least) -> str: 759 name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST" 760 return self.func(name, expression.this, *expression.expressions) 761 762 def generator_sql(self, expression: exp.Generator) -> str: 763 args = [] 764 rowcount = expression.args.get("rowcount") 765 timelimit = expression.args.get("timelimit") 766 767 if rowcount: 768 args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount)) 769 if timelimit: 770 args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit)) 771 772 return self.func("GENERATOR", *args) 773 774 def unnest_sql(self, expression: exp.Unnest) -> str: 775 unnest_alias = expression.args.get("alias") 776 offset = expression.args.get("offset") 777 778 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 779 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 780 781 columns = [ 782 exp.to_identifier("seq"), 783 exp.to_identifier("key"), 784 exp.to_identifier("path"), 785 offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"), 786 value, 787 exp.to_identifier("this"), 788 ] 789 790 if unnest_alias: 791 unnest_alias.set("columns", columns) 792 else: 793 unnest_alias = exp.TableAlias(this="_u", columns=columns) 794 795 table_input = self.sql(expression.expressions[0]) 796 if not table_input.startswith("INPUT =>"): 797 table_input = f"INPUT => {table_input}" 798 799 expression_parent = expression.parent 800 801 explode = ( 802 f"FLATTEN({table_input})" 803 if isinstance(expression_parent, exp.Lateral) 804 else f"TABLE(FLATTEN({table_input}))" 805 ) 806 alias = self.sql(unnest_alias) 807 alias = f" AS {alias}" if alias else "" 808 value = ( 809 "" 810 if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral)) 811 else f"{value} FROM " 812 ) 813 814 return f"{value}{explode}{alias}" 815 816 def undrop_sql(self, expression: exp.Undrop) -> str: 817 this = self.sql(expression, "this") 818 kind = expression.kind 819 rename = self.sql(expression, "rename") 820 rename = f" RENAME TO {rename}" if rename else "" 821 return f"UNDROP {kind} {this}{rename}" 822 823 def show_sql(self, expression: exp.Show) -> str: 824 terse = "TERSE " if expression.args.get("terse") else "" 825 iceberg = "ICEBERG " if expression.args.get("iceberg") else "" 826 history = " HISTORY" if expression.args.get("history") else "" 827 like = self.sql(expression, "like") 828 like = f" LIKE {like}" if like else "" 829 830 scope = self.sql(expression, "scope") 831 scope = f" {scope}" if scope else "" 832 833 scope_kind = self.sql(expression, "scope_kind") 834 if scope_kind: 835 scope_kind = f" IN {scope_kind}" 836 837 starts_with = self.sql(expression, "starts_with") 838 if starts_with: 839 starts_with = f" STARTS WITH {starts_with}" 840 841 limit = self.sql(expression, "limit") 842 843 from_ = self.sql(expression, "from_") 844 if from_: 845 from_ = f" FROM {from_}" 846 847 privileges = self.expressions(expression, key="privileges", flat=True) 848 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 849 850 return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}" 851 852 def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str: 853 if not expression.this: 854 return "ROW ACCESS" 855 on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else "" 856 return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}" 857 858 def describe_sql(self, expression: exp.Describe) -> str: 859 kind_value = expression.args.get("kind") or "TABLE" 860 861 properties = expression.args.get("properties") 862 if properties: 863 qualifier = self.expressions(properties, sep=" ") 864 kind = f" {qualifier} {kind_value}" 865 else: 866 kind = f" {kind_value}" 867 868 this = f" {self.sql(expression, 'this')}" 869 expressions = self.expressions(expression, flat=True) 870 expressions = f" {expressions}" if expressions else "" 871 return f"DESCRIBE{kind}{this}{expressions}" 872 873 def generatedasidentitycolumnconstraint_sql( 874 self, expression: exp.GeneratedAsIdentityColumnConstraint 875 ) -> str: 876 start = expression.args.get("start") 877 start = f" START {start}" if start else "" 878 increment = expression.args.get("increment") 879 increment = f" INCREMENT {increment}" if increment else "" 880 881 order = expression.args.get("order") 882 if order is not None: 883 order_clause = " ORDER" if order else " NOORDER" 884 else: 885 order_clause = "" 886 887 return f"AUTOINCREMENT{start}{increment}{order_clause}" 888 889 def struct_sql(self, expression: exp.Struct) -> str: 890 if len(expression.expressions) == 1: 891 arg = expression.expressions[0] 892 if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star): 893 # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object 894 return f"{{{self.sql(expression.expressions[0])}}}" 895 896 keys = [] 897 values = [] 898 899 for i, e in enumerate(expression.expressions): 900 if isinstance(e, exp.PropertyEQ): 901 keys.append( 902 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 903 ) 904 values.append(e.expression) 905 else: 906 keys.append(exp.Literal.string(f"_{i}")) 907 values.append(e) 908 909 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values))) 910 911 @unsupported_args("weight", "accuracy") 912 def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str: 913 return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile")) 914 915 def alterset_sql(self, expression: exp.AlterSet) -> str: 916 exprs = self.expressions(expression, flat=True) 917 exprs = f" {exprs}" if exprs else "" 918 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 919 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 920 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 921 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 922 tag = self.expressions(expression, key="tag", flat=True) 923 tag = f" TAG {tag}" if tag else "" 924 925 return f"SET{exprs}{file_format}{copy_options}{tag}" 926 927 def strtotime_sql(self, expression: exp.StrToTime): 928 # target_type is stored as a DataType instance 929 target_type = expression.args.get("target_type") 930 931 # Get the type enum from DataType instance or from type annotation 932 if isinstance(target_type, exp.DataType): 933 type_enum = target_type.this 934 elif expression.type: 935 type_enum = expression.type.this 936 else: 937 type_enum = exp.DType.TIMESTAMP 938 939 func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP") 940 941 return self.func( 942 f"{'TRY_' if expression.args.get('safe') else ''}{func_name}", 943 expression.this, 944 self.format_time(expression), 945 ) 946 947 def timestampsub_sql(self, expression: exp.TimestampSub): 948 return self.sql( 949 exp.TimestampAdd( 950 this=expression.this, 951 expression=expression.expression * -1, 952 unit=expression.unit, 953 ) 954 ) 955 956 def jsonextract_sql(self, expression: exp.JSONExtract): 957 this = expression.this 958 959 # JSON strings are valid coming from other dialects such as BQ so 960 # for these cases we PARSE_JSON preemptively 961 if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get( 962 "requires_json" 963 ): 964 this = exp.ParseJSON(this=this) 965 966 return self.func( 967 "GET_PATH", 968 this, 969 expression.expression, 970 ) 971 972 def timetostr_sql(self, expression: exp.TimeToStr) -> str: 973 this = expression.this 974 if this.is_string: 975 this = exp.cast(this, exp.DType.TIMESTAMP) 976 977 return self.func("TO_CHAR", this, self.format_time(expression)) 978 979 def datesub_sql(self, expression: exp.DateSub) -> str: 980 value = expression.expression 981 if value: 982 value.replace(value * (-1)) 983 else: 984 self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown") 985 986 return date_delta_sql("DATEADD")(self, expression) 987 988 def select_sql(self, expression: exp.Select) -> str: 989 limit = expression.args.get("limit") 990 offset = expression.args.get("offset") 991 if offset and not limit: 992 expression.limit(exp.Null(), copy=False) 993 return super().select_sql(expression) 994 995 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 996 is_materialized = expression.find(exp.MaterializedProperty) 997 copy_grants_property = expression.find(exp.CopyGrantsProperty) 998 999 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1000 # For materialized views, COPY GRANTS is located *before* the columns list 1001 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1002 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1003 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1004 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1005 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1006 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1007 1008 this_name = self.sql(expression.this, "this") 1009 copy_grants = self.sql(copy_grants_property) 1010 this_schema = self.schema_columns_sql(expression.this) 1011 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1012 1013 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1014 1015 return super().createable_sql(expression, locations) 1016 1017 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1018 this = expression.this 1019 1020 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1021 # and add it later as part of the WITHIN GROUP clause 1022 order = this if isinstance(this, exp.Order) else None 1023 if order: 1024 expression.set("this", order.this.pop()) 1025 1026 expr_sql = super().arrayagg_sql(expression) 1027 1028 if order: 1029 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1030 1031 return expr_sql 1032 1033 def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str: 1034 if expression.args.get("check_null"): 1035 return self.func("ARRAY_DISTINCT", expression.this) 1036 return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this)) 1037 1038 def arraytostring_sql(self, expression: exp.ArrayToString) -> str: 1039 return self.func("ARRAY_TO_STRING", expression.this, expression.expression) 1040 1041 def array_sql(self, expression: exp.Array) -> str: 1042 expressions = expression.expressions 1043 1044 first_expr = seq_get(expressions, 0) 1045 if isinstance(first_expr, exp.Select): 1046 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1047 if first_expr.text("kind").upper() == "STRUCT": 1048 object_construct_args = [] 1049 for expr in first_expr.expressions: 1050 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1051 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1052 name = expr.this if isinstance(expr, exp.Alias) else expr 1053 1054 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1055 1056 array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args)) 1057 1058 first_expr.set("kind", None) 1059 first_expr.set("expressions", [array_agg]) 1060 1061 return self.sql(first_expr.subquery()) 1062 1063 return inline_array_sql(self, expression) 1064 1065 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1066 zone = self.sql(expression, "this") 1067 if not zone: 1068 return super().currentdate_sql(expression) 1069 1070 expr = exp.Cast( 1071 this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()), 1072 to=exp.DataType(this=exp.DType.DATE), 1073 ) 1074 return self.sql(expr) 1075 1076 def dot_sql(self, expression: exp.Dot) -> str: 1077 this = expression.this 1078 1079 if not this.type: 1080 from sqlglot.optimizer.annotate_types import annotate_types 1081 1082 this = annotate_types(this, dialect=self.dialect) 1083 1084 if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT): 1085 # Generate colon notation for the top level STRUCT 1086 return f"{self.sql(this)}:{self.sql(expression, 'expression')}" 1087 1088 return super().dot_sql(expression) 1089 1090 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 1091 return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}" 1092 1093 def format_sql(self, expression: exp.Format) -> str: 1094 if expression.name.lower() == "%s" and len(expression.expressions) == 1: 1095 return self.func("TO_CHAR", expression.expressions[0]) 1096 1097 return self.function_fallback_sql(expression) 1098 1099 def splitpart_sql(self, expression: exp.SplitPart) -> str: 1100 # Set part_index to 1 if missing 1101 if not expression.args.get("delimiter"): 1102 expression.set("delimiter", exp.Literal.string(" ")) 1103 1104 if not expression.args.get("part_index"): 1105 expression.set("part_index", exp.Literal.number(1)) 1106 1107 return rename_func("SPLIT_PART")(self, expression) 1108 1109 def uniform_sql(self, expression: exp.Uniform) -> str: 1110 gen = expression.args.get("gen") 1111 seed = expression.args.get("seed") 1112 1113 # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed) 1114 if seed: 1115 gen = exp.Rand(this=seed) 1116 1117 # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM() 1118 if not gen: 1119 gen = exp.Rand() 1120 1121 return self.func("UNIFORM", expression.this, expression.expression, gen) 1122 1123 def window_sql(self, expression: exp.Window) -> str: 1124 spec = expression.args.get("spec") 1125 this = expression.this 1126 1127 if ( 1128 ( 1129 isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1130 or ( 1131 isinstance(this, (exp.RespectNulls, exp.IgnoreNulls)) 1132 and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1133 ) 1134 ) 1135 and spec 1136 and ( 1137 spec.text("kind").upper() == "ROWS" 1138 and spec.text("start").upper() == "UNBOUNDED" 1139 and spec.text("start_side").upper() == "PRECEDING" 1140 and spec.text("end").upper() == "UNBOUNDED" 1141 and spec.text("end_side").upper() == "FOLLOWING" 1142 ) 1143 ): 1144 # omit the default window from window ranking functions 1145 expression.set("spec", None) 1146 return super().window_sql(expression) 1147 1148 def filter_sql(self, expression: exp.Filter) -> str: 1149 # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an 1150 # equivalent conditional aggregation, i.e. wrap the input values in an IFF 1151 agg = expression.this 1152 agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this 1153 cond = expression.expression.this 1154 1155 if isinstance(agg, exp.WithinGroup): 1156 # Ordered-set aggregates take their input from the ORDER BY key, so the 1157 # condition has to wrap that instead of the aggregate's own argument 1158 if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)): 1159 for ordered in agg.expression.expressions: 1160 key = ordered.this 1161 key.replace(exp.If(this=cond.copy(), true=key.copy())) 1162 1163 return self.sql(agg) 1164 1165 # Besides the percentile functions, these are the only functions Snowflake 1166 # accepts WITHIN GROUP for, so anything else can't be rewritten correctly 1167 if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)): 1168 agg_arg = agg_arg.this 1169 else: 1170 self.unsupported("Unable to rewrite FILTER into the aggregate's arguments") 1171 return self.sql(agg) 1172 1173 # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF 1174 # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of 1175 # them, which Snowflake rejects. Use its native COUNT_IF instead. 1176 if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star: 1177 return self.func("COUNT_IF", cond) 1178 1179 # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the 1180 # condition has to wrap the values underneath them rather than the whole clause -- 1181 # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts. 1182 if isinstance(agg_arg, exp.Order): 1183 agg_arg = agg_arg.this 1184 1185 if isinstance(agg_arg, exp.Distinct): 1186 targets = agg_arg.expressions 1187 else: 1188 targets = [agg_arg] 1189 1190 for target in targets: 1191 target.replace(exp.If(this=cond.copy(), true=target.copy())) 1192 1193 return self.sql(agg) 1194 1195 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1196 # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only 1197 # accepts the value to aggregate as an argument: MODE(<expr>) 1198 if isinstance(expression.this, exp.Mode) and not expression.this.this: 1199 order = expression.expression 1200 if isinstance(order, exp.Order) and len(order.expressions) == 1: 1201 return self.sql(exp.Mode(this=order.expressions[0].this)) 1202 1203 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.BinaryColumnConstraint'>: <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.JSONBContainsTopKey'>: <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.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.aggregate.NthValue'>: <function nth_value_from_sql>, <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>>}
601 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 602 this = self.func("IDENTIFIER", expression.this) 603 if "expressions" in expression.args: 604 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 605 return self.func(this, *expression.expressions, normalize=False) 606 return this
608 def sortarray_sql(self, expression: exp.SortArray) -> str: 609 asc = expression.args.get("asc") 610 nulls_first = expression.args.get("nulls_first") 611 if asc == exp.false() and nulls_first == exp.true(): 612 nulls_first = None 613 return self.func("ARRAY_SORT", expression.this, asc, nulls_first)
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathRoot'>}
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: ClassVar =
{<class 'sqlglot.expressions.array.Map'>, <class 'sqlglot.expressions.array.Struct'>, <class 'sqlglot.expressions.array.StarMap'>, <class 'sqlglot.expressions.array.VarMap'>}
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:
662 def datatype_sql(self, expression: exp.DataType) -> str: 663 # Check if this is a FLOAT type nested inside a VECTOR type 664 # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types 665 # https://docs.snowflake.com/en/sql-reference/data-types-vector 666 if expression.is_type(exp.DType.DOUBLE): 667 parent = expression.parent 668 if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR): 669 # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE 670 return "FLOAT" 671 672 expressions = expression.expressions 673 if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES): 674 for field_type in expressions: 675 # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ] 676 if isinstance(field_type, exp.DataType): 677 return "OBJECT" 678 if ( 679 isinstance(field_type, exp.ColumnDef) 680 and field_type.this 681 and field_type.this.is_string 682 ): 683 # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides 684 # converting 'foo' into an identifier, we also need to quote it because these 685 # keys are case-sensitive. For example: 686 # 687 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct 688 # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL 689 field_type.this.replace(exp.to_identifier(field_type.name, quoted=True)) 690 691 return super().datatype_sql(expression)
693 def tonumber_sql(self, expression: exp.ToNumber) -> str: 694 precision = expression.args.get("precision") 695 scale = expression.args.get("scale") 696 697 default_precision = isinstance(precision, exp.Literal) and precision.name == "38" 698 default_scale = isinstance(scale, exp.Literal) and scale.name == "0" 699 700 if default_precision and default_scale: 701 precision = None 702 scale = None 703 elif default_scale: 704 scale = None 705 706 func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER" 707 708 return self.func( 709 func_name, 710 expression.this, 711 expression.args.get("format"), 712 precision, 713 scale, 714 )
def
timestampfromparts_sql(self, expression: sqlglot.expressions.temporal.TimestampFromParts) -> str:
716 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 717 milli = expression.args.get("milli") 718 if milli is not None: 719 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 720 expression.set("nano", milli_to_nano) 721 722 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
724 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 725 if expression.is_type(exp.DType.GEOGRAPHY): 726 return self.func("TO_GEOGRAPHY", expression.this) 727 if expression.is_type(exp.DType.GEOMETRY): 728 return self.func("TO_GEOMETRY", expression.this) 729 730 return super().cast_sql(expression, safe_prefix=safe_prefix)
732 def trycast_sql(self, expression: exp.TryCast) -> str: 733 value = expression.this 734 735 if value.type is None: 736 from sqlglot.optimizer.annotate_types import annotate_types 737 738 value = annotate_types(value, dialect=self.dialect) 739 740 # Snowflake requires that TRY_CAST's value be a string 741 # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or 742 # if we can deduce that the value is a string, then we can generate TRY_CAST 743 if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES): 744 return super().trycast_sql(expression) 745 746 return self.cast_sql(expression)
762 def generator_sql(self, expression: exp.Generator) -> str: 763 args = [] 764 rowcount = expression.args.get("rowcount") 765 timelimit = expression.args.get("timelimit") 766 767 if rowcount: 768 args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount)) 769 if timelimit: 770 args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit)) 771 772 return self.func("GENERATOR", *args)
774 def unnest_sql(self, expression: exp.Unnest) -> str: 775 unnest_alias = expression.args.get("alias") 776 offset = expression.args.get("offset") 777 778 unnest_alias_columns = unnest_alias.columns if unnest_alias else [] 779 value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value") 780 781 columns = [ 782 exp.to_identifier("seq"), 783 exp.to_identifier("key"), 784 exp.to_identifier("path"), 785 offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"), 786 value, 787 exp.to_identifier("this"), 788 ] 789 790 if unnest_alias: 791 unnest_alias.set("columns", columns) 792 else: 793 unnest_alias = exp.TableAlias(this="_u", columns=columns) 794 795 table_input = self.sql(expression.expressions[0]) 796 if not table_input.startswith("INPUT =>"): 797 table_input = f"INPUT => {table_input}" 798 799 expression_parent = expression.parent 800 801 explode = ( 802 f"FLATTEN({table_input})" 803 if isinstance(expression_parent, exp.Lateral) 804 else f"TABLE(FLATTEN({table_input}))" 805 ) 806 alias = self.sql(unnest_alias) 807 alias = f" AS {alias}" if alias else "" 808 value = ( 809 "" 810 if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral)) 811 else f"{value} FROM " 812 ) 813 814 return f"{value}{explode}{alias}"
823 def show_sql(self, expression: exp.Show) -> str: 824 terse = "TERSE " if expression.args.get("terse") else "" 825 iceberg = "ICEBERG " if expression.args.get("iceberg") else "" 826 history = " HISTORY" if expression.args.get("history") else "" 827 like = self.sql(expression, "like") 828 like = f" LIKE {like}" if like else "" 829 830 scope = self.sql(expression, "scope") 831 scope = f" {scope}" if scope else "" 832 833 scope_kind = self.sql(expression, "scope_kind") 834 if scope_kind: 835 scope_kind = f" IN {scope_kind}" 836 837 starts_with = self.sql(expression, "starts_with") 838 if starts_with: 839 starts_with = f" STARTS WITH {starts_with}" 840 841 limit = self.sql(expression, "limit") 842 843 from_ = self.sql(expression, "from_") 844 if from_: 845 from_ = f" FROM {from_}" 846 847 privileges = self.expressions(expression, key="privileges", flat=True) 848 privileges = f" WITH PRIVILEGES {privileges}" if privileges else "" 849 850 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:
858 def describe_sql(self, expression: exp.Describe) -> str: 859 kind_value = expression.args.get("kind") or "TABLE" 860 861 properties = expression.args.get("properties") 862 if properties: 863 qualifier = self.expressions(properties, sep=" ") 864 kind = f" {qualifier} {kind_value}" 865 else: 866 kind = f" {kind_value}" 867 868 this = f" {self.sql(expression, 'this')}" 869 expressions = self.expressions(expression, flat=True) 870 expressions = f" {expressions}" if expressions else "" 871 return f"DESCRIBE{kind}{this}{expressions}"
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
873 def generatedasidentitycolumnconstraint_sql( 874 self, expression: exp.GeneratedAsIdentityColumnConstraint 875 ) -> str: 876 start = expression.args.get("start") 877 start = f" START {start}" if start else "" 878 increment = expression.args.get("increment") 879 increment = f" INCREMENT {increment}" if increment else "" 880 881 order = expression.args.get("order") 882 if order is not None: 883 order_clause = " ORDER" if order else " NOORDER" 884 else: 885 order_clause = "" 886 887 return f"AUTOINCREMENT{start}{increment}{order_clause}"
889 def struct_sql(self, expression: exp.Struct) -> str: 890 if len(expression.expressions) == 1: 891 arg = expression.expressions[0] 892 if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star): 893 # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object 894 return f"{{{self.sql(expression.expressions[0])}}}" 895 896 keys = [] 897 values = [] 898 899 for i, e in enumerate(expression.expressions): 900 if isinstance(e, exp.PropertyEQ): 901 keys.append( 902 exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this 903 ) 904 values.append(e.expression) 905 else: 906 keys.append(exp.Literal.string(f"_{i}")) 907 values.append(e) 908 909 return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
@unsupported_args('weight', 'accuracy')
def
approxquantile_sql(self, expression: sqlglot.expressions.aggregate.ApproxQuantile) -> str:
915 def alterset_sql(self, expression: exp.AlterSet) -> str: 916 exprs = self.expressions(expression, flat=True) 917 exprs = f" {exprs}" if exprs else "" 918 file_format = self.expressions(expression, key="file_format", flat=True, sep=" ") 919 file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else "" 920 copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ") 921 copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else "" 922 tag = self.expressions(expression, key="tag", flat=True) 923 tag = f" TAG {tag}" if tag else "" 924 925 return f"SET{exprs}{file_format}{copy_options}{tag}"
927 def strtotime_sql(self, expression: exp.StrToTime): 928 # target_type is stored as a DataType instance 929 target_type = expression.args.get("target_type") 930 931 # Get the type enum from DataType instance or from type annotation 932 if isinstance(target_type, exp.DataType): 933 type_enum = target_type.this 934 elif expression.type: 935 type_enum = expression.type.this 936 else: 937 type_enum = exp.DType.TIMESTAMP 938 939 func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP") 940 941 return self.func( 942 f"{'TRY_' if expression.args.get('safe') else ''}{func_name}", 943 expression.this, 944 self.format_time(expression), 945 )
956 def jsonextract_sql(self, expression: exp.JSONExtract): 957 this = expression.this 958 959 # JSON strings are valid coming from other dialects such as BQ so 960 # for these cases we PARSE_JSON preemptively 961 if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get( 962 "requires_json" 963 ): 964 this = exp.ParseJSON(this=this) 965 966 return self.func( 967 "GET_PATH", 968 this, 969 expression.expression, 970 )
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
995 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 996 is_materialized = expression.find(exp.MaterializedProperty) 997 copy_grants_property = expression.find(exp.CopyGrantsProperty) 998 999 if expression.kind == "VIEW" and is_materialized and copy_grants_property: 1000 # For materialized views, COPY GRANTS is located *before* the columns list 1001 # This is in contrast to normal views where COPY GRANTS is located *after* the columns list 1002 # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected 1003 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax 1004 # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax 1005 post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA] 1006 post_schema_properties.pop(post_schema_properties.index(copy_grants_property)) 1007 1008 this_name = self.sql(expression.this, "this") 1009 copy_grants = self.sql(copy_grants_property) 1010 this_schema = self.schema_columns_sql(expression.this) 1011 this_schema = f"{self.sep()}{this_schema}" if this_schema else "" 1012 1013 return f"{this_name}{self.sep()}{copy_grants}{this_schema}" 1014 1015 return super().createable_sql(expression, locations)
1017 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 1018 this = expression.this 1019 1020 # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG 1021 # and add it later as part of the WITHIN GROUP clause 1022 order = this if isinstance(this, exp.Order) else None 1023 if order: 1024 expression.set("this", order.this.pop()) 1025 1026 expr_sql = super().arrayagg_sql(expression) 1027 1028 if order: 1029 expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order)) 1030 1031 return expr_sql
1041 def array_sql(self, expression: exp.Array) -> str: 1042 expressions = expression.expressions 1043 1044 first_expr = seq_get(expressions, 0) 1045 if isinstance(first_expr, exp.Select): 1046 # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo)) 1047 if first_expr.text("kind").upper() == "STRUCT": 1048 object_construct_args = [] 1049 for expr in first_expr.expressions: 1050 # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo) 1051 # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo) 1052 name = expr.this if isinstance(expr, exp.Alias) else expr 1053 1054 object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name]) 1055 1056 array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args)) 1057 1058 first_expr.set("kind", None) 1059 first_expr.set("expressions", [array_agg]) 1060 1061 return self.sql(first_expr.subquery()) 1062 1063 return inline_array_sql(self, expression)
1065 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 1066 zone = self.sql(expression, "this") 1067 if not zone: 1068 return super().currentdate_sql(expression) 1069 1070 expr = exp.Cast( 1071 this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()), 1072 to=exp.DataType(this=exp.DType.DATE), 1073 ) 1074 return self.sql(expr)
1076 def dot_sql(self, expression: exp.Dot) -> str: 1077 this = expression.this 1078 1079 if not this.type: 1080 from sqlglot.optimizer.annotate_types import annotate_types 1081 1082 this = annotate_types(this, dialect=self.dialect) 1083 1084 if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT): 1085 # Generate colon notation for the top level STRUCT 1086 return f"{self.sql(this)}:{self.sql(expression, 'expression')}" 1087 1088 return super().dot_sql(expression)
1099 def splitpart_sql(self, expression: exp.SplitPart) -> str: 1100 # Set part_index to 1 if missing 1101 if not expression.args.get("delimiter"): 1102 expression.set("delimiter", exp.Literal.string(" ")) 1103 1104 if not expression.args.get("part_index"): 1105 expression.set("part_index", exp.Literal.number(1)) 1106 1107 return rename_func("SPLIT_PART")(self, expression)
1109 def uniform_sql(self, expression: exp.Uniform) -> str: 1110 gen = expression.args.get("gen") 1111 seed = expression.args.get("seed") 1112 1113 # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed) 1114 if seed: 1115 gen = exp.Rand(this=seed) 1116 1117 # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM() 1118 if not gen: 1119 gen = exp.Rand() 1120 1121 return self.func("UNIFORM", expression.this, expression.expression, gen)
1123 def window_sql(self, expression: exp.Window) -> str: 1124 spec = expression.args.get("spec") 1125 this = expression.this 1126 1127 if ( 1128 ( 1129 isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1130 or ( 1131 isinstance(this, (exp.RespectNulls, exp.IgnoreNulls)) 1132 and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME) 1133 ) 1134 ) 1135 and spec 1136 and ( 1137 spec.text("kind").upper() == "ROWS" 1138 and spec.text("start").upper() == "UNBOUNDED" 1139 and spec.text("start_side").upper() == "PRECEDING" 1140 and spec.text("end").upper() == "UNBOUNDED" 1141 and spec.text("end_side").upper() == "FOLLOWING" 1142 ) 1143 ): 1144 # omit the default window from window ranking functions 1145 expression.set("spec", None) 1146 return super().window_sql(expression)
1148 def filter_sql(self, expression: exp.Filter) -> str: 1149 # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an 1150 # equivalent conditional aggregation, i.e. wrap the input values in an IFF 1151 agg = expression.this 1152 agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this 1153 cond = expression.expression.this 1154 1155 if isinstance(agg, exp.WithinGroup): 1156 # Ordered-set aggregates take their input from the ORDER BY key, so the 1157 # condition has to wrap that instead of the aggregate's own argument 1158 if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)): 1159 for ordered in agg.expression.expressions: 1160 key = ordered.this 1161 key.replace(exp.If(this=cond.copy(), true=key.copy())) 1162 1163 return self.sql(agg) 1164 1165 # Besides the percentile functions, these are the only functions Snowflake 1166 # accepts WITHIN GROUP for, so anything else can't be rewritten correctly 1167 if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)): 1168 agg_arg = agg_arg.this 1169 else: 1170 self.unsupported("Unable to rewrite FILTER into the aggregate's arguments") 1171 return self.sql(agg) 1172 1173 # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF 1174 # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of 1175 # them, which Snowflake rejects. Use its native COUNT_IF instead. 1176 if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star: 1177 return self.func("COUNT_IF", cond) 1178 1179 # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the 1180 # condition has to wrap the values underneath them rather than the whole clause -- 1181 # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts. 1182 if isinstance(agg_arg, exp.Order): 1183 agg_arg = agg_arg.this 1184 1185 if isinstance(agg_arg, exp.Distinct): 1186 targets = agg_arg.expressions 1187 else: 1188 targets = [agg_arg] 1189 1190 for target in targets: 1191 target.replace(exp.If(this=cond.copy(), true=target.copy())) 1192 1193 return self.sql(agg)
1195 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 1196 # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only 1197 # accepts the value to aggregate as an argument: MODE(<expr>) 1198 if isinstance(expression.this, exp.Mode) and not expression.this.this: 1199 order = expression.expression 1200 if isinstance(order, exp.Order) and len(order.expressions) == 1: 1201 return self.sql(exp.Mode(this=order.expressions[0].this)) 1202 1203 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
- SUPPORTS_GROUPING_SETS_AS_SUFFIX
- 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
- SUPPORTS_ALTER_COLUMN_NULLABILITY
- SUPPORTS_ALTER_COLUMN_IF_EXISTS
- 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
- MOD_OPERATOR
- MOD_PAREN_PARENT_TYPES
- 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
- nthvalue_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
- casestatement_sql
- whileblock_sql
- loopblock_sql
- repeatblock_sql
- leave_sql
- iterate_sql
- execute_sql
- executesql_sql
- altermodifysqlsecurity_sql
- usingproperty_sql
- renameindex_sql