1from__future__importannotations 2 3fromitertoolsimportproduct 4 5fromsqlglotimportexp,jsonpath,tokens 6fromsqlglot.dialects.dialectimport( 7Dialect, 8NormalizationStrategy, 9) 10fromsqlglot.generators.snowflakeimportSnowflakeGenerator 11fromsqlglot.parsers.snowflakeimport( 12SnowflakeParser, 13) 14fromsqlglot.tokensimportTokenType 15fromsqlglot.typing.snowflakeimportEXPRESSION_METADATA 16 17 18classSnowflake(Dialect): 19# https://docs.snowflake.com/en/sql-reference/identifiers-syntax 20NORMALIZATION_STRATEGY=NormalizationStrategy.UPPERCASE 21# https://docs.snowflake.com/en/sql-reference/data-types-text#escape-sequences 22UNESCAPED_SEQUENCES={"\\a":"a","\\v":"v"} 23NULL_ORDERING="nulls_are_large" 24TIME_FORMAT="'YYYY-MM-DD HH24:MI:SS'" 25SUPPORTS_USER_DEFINED_TYPES=False 26PREFER_CTE_ALIAS_COLUMN=True 27SUPPORTS_POSITIONAL_COLUMN_REFS=True 28TABLESAMPLE_SIZE_IS_PERCENT=True 29COPY_PARAMS_ARE_CSV=False 30ARRAY_AGG_INCLUDES_NULLS=None 31ARRAY_FUNCS_PROPAGATES_NULLS=True 32ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN=False 33ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN=False 34TRY_CAST_REQUIRES_STRING=True 35SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS=True 36LEAST_GREATEST_IGNORES_NULLS=False 37UUID_IS_STRING_TYPE=True 38STAR_ILIKE_BACKSLASH_ESCAPE=True 39 40# https://docs.snowflake.com/en/sql-reference/functions/split_to_table 41DEFAULT_FUNCTIONS_COLUMN_NAMES={ 42exp.SplitToTable:("seq","index","value"), 43exp.StrtokSplitToTable:("seq","index","value"), 44} 45 46EXPRESSION_METADATA=EXPRESSION_METADATA.copy() 47 48# https://docs.snowflake.com/en/en/sql-reference/functions/initcap 49INITCAP_DEFAULT_DELIMITER_CHARS=' \t\n\r\f\v!?@"^#$&~_,.:;+\\-*%/|\\[\\](){}<>' 50 51INVERSE_TIME_MAPPING={ 52"%d":"DD", 53"%-d":"DD", 54"%a":"DY", 55"T":"T",# in TIME_MAPPING we map '"T"' with the double quotes to 'T', and we want to prevent 'T' from being mapped back to '"T"' so that 'AUTO' doesn't become 'AU"T"O' 56} 57 58TIME_MAPPING={ 59"".join(chars):value 60forkey,valuein{ 61"YYYY":"%Y", 62"YY":"%y", 63"MMMM":"%B", 64"MON":"%b", 65"MM":"%m", 66"DD":"%d", 67"DY":"%a", 68"HH24":"%H", 69"HH12":"%I", 70"MI":"%M", 71"SS":"%S", 72"FF":"%f_nine",# %f_ internal representation with precision specified 73"FF0":"%f_zero", 74"FF1":"%f_one", 75"FF2":"%f_two", 76"FF3":"%f_three", 77"FF4":"%f_four", 78"FF5":"%f_five", 79"FF6":"%f", 80"FF7":"%f_seven", 81"FF8":"%f_eight", 82"FF9":"%f_nine", 83"TZHTZM":"%z", 84"TZH:TZM":"%:z",# internal representation for ±HH:MM 85"TZH":"%-z",# internal representation ±HH 86# Seems like Snowflake treats AM/PM in the format string as equivalent, 87# only the time (stamp) value's AM/PM affects the output 88"AM":"%p", 89"PM":"%p", 90}.items() 91forcharsinproduct(*zip(key.upper(),key.lower())) 92}|{'"T"':"T"} 93 94DATE_PART_MAPPING={ 95**Dialect.DATE_PART_MAPPING, 96"ISOWEEK":"WEEKISO", 97# The base Dialect maps EPOCH_SECOND -> EPOCH, but we need to preserve 98# EPOCH_SECOND as a distinct value for two reasons: 99# 1. Type annotation: EPOCH_SECOND returns BIGINT, while EPOCH returns DOUBLE100# 2. Transpilation: DuckDB's EPOCH() returns float, so we cast EPOCH_SECOND101# to BIGINT to match Snowflake's integer behavior102# Without this override, EXTRACT(EPOCH_SECOND FROM ts) would be normalized103# to EXTRACT(EPOCH FROM ts) and lose the integer semantics.104"EPOCH_SECOND":"EPOCH_SECOND",105"EPOCH_SECONDS":"EPOCH_SECOND",106}107108PSEUDOCOLUMNS={"LEVEL"}109110defcan_quote(self,identifier:exp.Identifier,identify:str|bool="safe")->bool:111# This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an112# unquoted DUAL keyword in a special way and does not map it to a user-defined table113returnsuper().can_quote(identifier,identify)andnot(114isinstance(identifier.parent,exp.Table)115andnotidentifier.quoted116andidentifier.name.lower()=="dual"117)118119classJSONPathTokenizer(jsonpath.JSONPathTokenizer):120SINGLE_TOKENS=jsonpath.JSONPathTokenizer.SINGLE_TOKENS.copy()121SINGLE_TOKENS.pop("$")122123Parser=SnowflakeParser124125classTokenizer(tokens.Tokenizer):126STRING_ESCAPES=["\\","'"]127NUMERIC_ESCAPES={"x":(16,2,2,0xFF),"u":(16,4,4,0xFFFF),"0":(8,1,3,0xFF)}128DROP_UNKNOWN_ESCAPES=True129HEX_STRINGS=[("x'","'"),("X'","'")]130RAW_STRINGS=["$$"]131COMMENTS=["--","//",("/*","*/")]132NESTED_COMMENTS=False133134KEYWORDS={135**tokens.Tokenizer.KEYWORDS,136"(+)":TokenType.JOIN_MARKER,137"BYTEINT":TokenType.INT,138"FILE://":TokenType.URI_START,139"FILE FORMAT":TokenType.FILE_FORMAT,140"GET":TokenType.GET,141"INTEGRATION":TokenType.INTEGRATION,142"LS":TokenType.LIST,143"MATCH_CONDITION":TokenType.MATCH_CONDITION,144"MATCH_RECOGNIZE":TokenType.MATCH_RECOGNIZE,145"MINUS":TokenType.EXCEPT,146"NCHAR VARYING":TokenType.VARCHAR,147"PACKAGE":TokenType.PACKAGE,148"POLICY":TokenType.POLICY,149"POOL":TokenType.POOL,150"PUT":TokenType.PUT,151"UNDROP":TokenType.UNDROP,152"REMOVE":TokenType.COMMAND,153"RM":TokenType.COMMAND,154"ROLE":TokenType.ROLE,155"RULE":TokenType.RULE,156"SAMPLE":TokenType.TABLE_SAMPLE,157"SEMANTIC VIEW":TokenType.SEMANTIC_VIEW,158"SQL_DOUBLE":TokenType.DOUBLE,159"SQL_VARCHAR":TokenType.VARCHAR,160"STAGE":TokenType.STAGE,161"STORAGE INTEGRATION":TokenType.STORAGE_INTEGRATION,162"STREAMLIT":TokenType.STREAMLIT,163"TAG":TokenType.TAG,164"TIMESTAMP_TZ":TokenType.TIMESTAMPTZ,165"TOP":TokenType.TOP,166"VOLUME":TokenType.VOLUME,167"WAREHOUSE":TokenType.WAREHOUSE,168# https://docs.snowflake.com/en/sql-reference/data-types-numeric#float169# FLOAT is a synonym for DOUBLE in Snowflake170"FLOAT":TokenType.DOUBLE,171}172KEYWORDS.pop("/*+")173KEYWORDS.pop("GLOB")174KEYWORDS.pop("RETURNING")175176SINGLE_TOKENS={177**tokens.Tokenizer.SINGLE_TOKENS,178"$":TokenType.PARAMETER,179"!":TokenType.EXCLAMATION,180}181182VAR_SINGLE_TOKENS={"$"}183184COMMANDS=tokens.Tokenizer.COMMANDS-{TokenType.SHOW}185186Generator=SnowflakeGenerator
classSnowflake(sqlglot.dialects.dialect.Dialect):
19classSnowflake(Dialect): 20# https://docs.snowflake.com/en/sql-reference/identifiers-syntax 21NORMALIZATION_STRATEGY=NormalizationStrategy.UPPERCASE 22# https://docs.snowflake.com/en/sql-reference/data-types-text#escape-sequences 23UNESCAPED_SEQUENCES={"\\a":"a","\\v":"v"} 24NULL_ORDERING="nulls_are_large" 25TIME_FORMAT="'YYYY-MM-DD HH24:MI:SS'" 26SUPPORTS_USER_DEFINED_TYPES=False 27PREFER_CTE_ALIAS_COLUMN=True 28SUPPORTS_POSITIONAL_COLUMN_REFS=True 29TABLESAMPLE_SIZE_IS_PERCENT=True 30COPY_PARAMS_ARE_CSV=False 31ARRAY_AGG_INCLUDES_NULLS=None 32ARRAY_FUNCS_PROPAGATES_NULLS=True 33ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN=False 34ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN=False 35TRY_CAST_REQUIRES_STRING=True 36SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS=True 37LEAST_GREATEST_IGNORES_NULLS=False 38UUID_IS_STRING_TYPE=True 39STAR_ILIKE_BACKSLASH_ESCAPE=True 40 41# https://docs.snowflake.com/en/sql-reference/functions/split_to_table 42DEFAULT_FUNCTIONS_COLUMN_NAMES={ 43exp.SplitToTable:("seq","index","value"), 44exp.StrtokSplitToTable:("seq","index","value"), 45} 46 47EXPRESSION_METADATA=EXPRESSION_METADATA.copy() 48 49# https://docs.snowflake.com/en/en/sql-reference/functions/initcap 50INITCAP_DEFAULT_DELIMITER_CHARS=' \t\n\r\f\v!?@"^#$&~_,.:;+\\-*%/|\\[\\](){}<>' 51 52INVERSE_TIME_MAPPING={ 53"%d":"DD", 54"%-d":"DD", 55"%a":"DY", 56"T":"T",# in TIME_MAPPING we map '"T"' with the double quotes to 'T', and we want to prevent 'T' from being mapped back to '"T"' so that 'AUTO' doesn't become 'AU"T"O' 57} 58 59TIME_MAPPING={ 60"".join(chars):value 61forkey,valuein{ 62"YYYY":"%Y", 63"YY":"%y", 64"MMMM":"%B", 65"MON":"%b", 66"MM":"%m", 67"DD":"%d", 68"DY":"%a", 69"HH24":"%H", 70"HH12":"%I", 71"MI":"%M", 72"SS":"%S", 73"FF":"%f_nine",# %f_ internal representation with precision specified 74"FF0":"%f_zero", 75"FF1":"%f_one", 76"FF2":"%f_two", 77"FF3":"%f_three", 78"FF4":"%f_four", 79"FF5":"%f_five", 80"FF6":"%f", 81"FF7":"%f_seven", 82"FF8":"%f_eight", 83"FF9":"%f_nine", 84"TZHTZM":"%z", 85"TZH:TZM":"%:z",# internal representation for ±HH:MM 86"TZH":"%-z",# internal representation ±HH 87# Seems like Snowflake treats AM/PM in the format string as equivalent, 88# only the time (stamp) value's AM/PM affects the output 89"AM":"%p", 90"PM":"%p", 91}.items() 92forcharsinproduct(*zip(key.upper(),key.lower())) 93}|{'"T"':"T"} 94 95DATE_PART_MAPPING={ 96**Dialect.DATE_PART_MAPPING, 97"ISOWEEK":"WEEKISO", 98# The base Dialect maps EPOCH_SECOND -> EPOCH, but we need to preserve 99# EPOCH_SECOND as a distinct value for two reasons:100# 1. Type annotation: EPOCH_SECOND returns BIGINT, while EPOCH returns DOUBLE101# 2. Transpilation: DuckDB's EPOCH() returns float, so we cast EPOCH_SECOND102# to BIGINT to match Snowflake's integer behavior103# Without this override, EXTRACT(EPOCH_SECOND FROM ts) would be normalized104# to EXTRACT(EPOCH FROM ts) and lose the integer semantics.105"EPOCH_SECOND":"EPOCH_SECOND",106"EPOCH_SECONDS":"EPOCH_SECOND",107}108109PSEUDOCOLUMNS={"LEVEL"}110111defcan_quote(self,identifier:exp.Identifier,identify:str|bool="safe")->bool:112# This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an113# unquoted DUAL keyword in a special way and does not map it to a user-defined table114returnsuper().can_quote(identifier,identify)andnot(115isinstance(identifier.parent,exp.Table)116andnotidentifier.quoted117andidentifier.name.lower()=="dual"118)119120classJSONPathTokenizer(jsonpath.JSONPathTokenizer):121SINGLE_TOKENS=jsonpath.JSONPathTokenizer.SINGLE_TOKENS.copy()122SINGLE_TOKENS.pop("$")123124Parser=SnowflakeParser125126classTokenizer(tokens.Tokenizer):127STRING_ESCAPES=["\\","'"]128NUMERIC_ESCAPES={"x":(16,2,2,0xFF),"u":(16,4,4,0xFFFF),"0":(8,1,3,0xFF)}129DROP_UNKNOWN_ESCAPES=True130HEX_STRINGS=[("x'","'"),("X'","'")]131RAW_STRINGS=["$$"]132COMMENTS=["--","//",("/*","*/")]133NESTED_COMMENTS=False134135KEYWORDS={136**tokens.Tokenizer.KEYWORDS,137"(+)":TokenType.JOIN_MARKER,138"BYTEINT":TokenType.INT,139"FILE://":TokenType.URI_START,140"FILE FORMAT":TokenType.FILE_FORMAT,141"GET":TokenType.GET,142"INTEGRATION":TokenType.INTEGRATION,143"LS":TokenType.LIST,144"MATCH_CONDITION":TokenType.MATCH_CONDITION,145"MATCH_RECOGNIZE":TokenType.MATCH_RECOGNIZE,146"MINUS":TokenType.EXCEPT,147"NCHAR VARYING":TokenType.VARCHAR,148"PACKAGE":TokenType.PACKAGE,149"POLICY":TokenType.POLICY,150"POOL":TokenType.POOL,151"PUT":TokenType.PUT,152"UNDROP":TokenType.UNDROP,153"REMOVE":TokenType.COMMAND,154"RM":TokenType.COMMAND,155"ROLE":TokenType.ROLE,156"RULE":TokenType.RULE,157"SAMPLE":TokenType.TABLE_SAMPLE,158"SEMANTIC VIEW":TokenType.SEMANTIC_VIEW,159"SQL_DOUBLE":TokenType.DOUBLE,160"SQL_VARCHAR":TokenType.VARCHAR,161"STAGE":TokenType.STAGE,162"STORAGE INTEGRATION":TokenType.STORAGE_INTEGRATION,163"STREAMLIT":TokenType.STREAMLIT,164"TAG":TokenType.TAG,165"TIMESTAMP_TZ":TokenType.TIMESTAMPTZ,166"TOP":TokenType.TOP,167"VOLUME":TokenType.VOLUME,168"WAREHOUSE":TokenType.WAREHOUSE,169# https://docs.snowflake.com/en/sql-reference/data-types-numeric#float170# FLOAT is a synonym for DOUBLE in Snowflake171"FLOAT":TokenType.DOUBLE,172}173KEYWORDS.pop("/*+")174KEYWORDS.pop("GLOB")175KEYWORDS.pop("RETURNING")176177SINGLE_TOKENS={178**tokens.Tokenizer.SINGLE_TOKENS,179"$":TokenType.PARAMETER,180"!":TokenType.EXCLAMATION,181}182183VAR_SINGLE_TOKENS={"$"}184185COMMANDS=tokens.Tokenizer.COMMANDS-{TokenType.SHOW}186187Generator=SnowflakeGenerator
Mapping of an escaped sequence (\n) to its unescaped version ().
NULL_ORDERING =
'nulls_are_large'
Default NULL ordering method to use if not explicitly set.
Possible values: "nulls_are_small", "nulls_are_large", "nulls_are_last"
TIME_FORMAT =
"'YYYY-MM-DD HH24:MI:SS'"
SUPPORTS_USER_DEFINED_TYPES =
False
Whether user-defined data types are supported.
PREFER_CTE_ALIAS_COLUMN =
True
Some dialects, such as Snowflake, allow you to reference a CTE column alias in the
HAVING clause of the CTE. This flag will cause the CTE alias columns to override
any projection aliases in the subquery.
For example,
WITH y(c) AS (
SELECT SUM(a) FROM (SELECT 1 a) AS x HAVING c > 0
) SELECT c FROM y;
will be rewritten as
WITH y(c) AS (
SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0
) SELECT c FROM y;
SUPPORTS_POSITIONAL_COLUMN_REFS =
True
Whether qualified $N references the Nth column of their source.
TABLESAMPLE_SIZE_IS_PERCENT =
True
Whether a size in the table sample clause represents percentage.
COPY_PARAMS_ARE_CSV =
False
Whether COPY statement parameters are separated by comma or whitespace
ARRAY_AGG_INCLUDES_NULLS: bool | None =
None
Whether ArrayAgg needs to filter NULL values.
ARRAY_FUNCS_PROPAGATES_NULLS =
True
Whether Array update functions return NULL when the input array is NULL.
ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN =
False
ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN =
False
TRY_CAST_REQUIRES_STRING: bool | None =
True
SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS =
True
Whether alias references are allowed in JOIN ... ON clauses.
Most dialects do not support this, but Snowflake allows alias expansion in the JOIN ... ON
clause (and almost everywhere else)
For example, in Snowflake:
SELECT a.id AS user_id FROM a JOIN b ON user_id = b.id -- VALID
Whether a UUID is considered a string or a UUID type.
STAR_ILIKE_BACKSLASH_ESCAPE =
True
Whether a backslash in a SELECT * ILIKE '<pattern>' filter escapes the following character,
so that e.g. \_ matches a literal underscore (Snowflake). When False, backslashes in the
pattern are matched literally (DuckDB).
Maps function expressions to their default output column name(s).
For example, in Postgres, generate_series function outputs a column named "generate_series" by default,
so we map the ExplodingGenerateSeries expression to "generate_series" string.
111defcan_quote(self,identifier:exp.Identifier,identify:str|bool="safe")->bool:112# This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an113# unquoted DUAL keyword in a special way and does not map it to a user-defined table114returnsuper().can_quote(identifier,identify)andnot(115isinstance(identifier.parent,exp.Table)116andnotidentifier.quoted117andidentifier.name.lower()=="dual"118)
Checks if an identifier can be quoted
Arguments:
identifier: The identifier to check.
identify:True: Always returns True except for certain cases.
"safe": Only returns True if the identifier is case-insensitive.
"unsafe": Only returns True if the identifier is case-sensitive.
126classTokenizer(tokens.Tokenizer):127STRING_ESCAPES=["\\","'"]128NUMERIC_ESCAPES={"x":(16,2,2,0xFF),"u":(16,4,4,0xFFFF),"0":(8,1,3,0xFF)}129DROP_UNKNOWN_ESCAPES=True130HEX_STRINGS=[("x'","'"),("X'","'")]131RAW_STRINGS=["$$"]132COMMENTS=["--","//",("/*","*/")]133NESTED_COMMENTS=False134135KEYWORDS={136**tokens.Tokenizer.KEYWORDS,137"(+)":TokenType.JOIN_MARKER,138"BYTEINT":TokenType.INT,139"FILE://":TokenType.URI_START,140"FILE FORMAT":TokenType.FILE_FORMAT,141"GET":TokenType.GET,142"INTEGRATION":TokenType.INTEGRATION,143"LS":TokenType.LIST,144"MATCH_CONDITION":TokenType.MATCH_CONDITION,145"MATCH_RECOGNIZE":TokenType.MATCH_RECOGNIZE,146"MINUS":TokenType.EXCEPT,147"NCHAR VARYING":TokenType.VARCHAR,148"PACKAGE":TokenType.PACKAGE,149"POLICY":TokenType.POLICY,150"POOL":TokenType.POOL,151"PUT":TokenType.PUT,152"UNDROP":TokenType.UNDROP,153"REMOVE":TokenType.COMMAND,154"RM":TokenType.COMMAND,155"ROLE":TokenType.ROLE,156"RULE":TokenType.RULE,157"SAMPLE":TokenType.TABLE_SAMPLE,158"SEMANTIC VIEW":TokenType.SEMANTIC_VIEW,159"SQL_DOUBLE":TokenType.DOUBLE,160"SQL_VARCHAR":TokenType.VARCHAR,161"STAGE":TokenType.STAGE,162"STORAGE INTEGRATION":TokenType.STORAGE_INTEGRATION,163"STREAMLIT":TokenType.STREAMLIT,164"TAG":TokenType.TAG,165"TIMESTAMP_TZ":TokenType.TIMESTAMPTZ,166"TOP":TokenType.TOP,167"VOLUME":TokenType.VOLUME,168"WAREHOUSE":TokenType.WAREHOUSE,169# https://docs.snowflake.com/en/sql-reference/data-types-numeric#float170# FLOAT is a synonym for DOUBLE in Snowflake171"FLOAT":TokenType.DOUBLE,172}173KEYWORDS.pop("/*+")174KEYWORDS.pop("GLOB")175KEYWORDS.pop("RETURNING")176177SINGLE_TOKENS={178**tokens.Tokenizer.SINGLE_TOKENS,179"$":TokenType.PARAMETER,180"!":TokenType.EXCLAMATION,181}182183VAR_SINGLE_TOKENS={"$"}184185COMMANDS=tokens.Tokenizer.COMMANDS-{TokenType.SHOW}