sqlglot.dialects.athena
1from __future__ import annotations 2 3 4from sqlglot import tokens 5from sqlglot.dialects.dialect import DialectType 6from sqlglot.errors import TokenError 7from sqlglot.generators.athena import AthenaGenerator 8from sqlglot.parsers.athena import AthenaParser 9from sqlglot.tokens import TokenType, Token 10from sqlglot.dialects.trino import Trino 11from sqlglot.dialects.hive import Hive 12 13 14class Athena(Trino): 15 """ 16 Over the years, it looks like AWS has taken various execution engines, bolted on AWS-specific 17 modifications and then built the Athena service around them. 18 19 Thus, Athena is not simply hosted Trino, it's more like a router that routes SQL queries to an 20 execution engine depending on the query type. 21 22 As at 2024-09-10, assuming your Athena workgroup is configured to use "Athena engine version 3", 23 the following engines exist: 24 25 Hive: 26 - Accepts mostly the same syntax as Hadoop / Hive 27 - Uses backticks to quote identifiers 28 - Has a distinctive DDL syntax (around things like setting table properties, storage locations etc) 29 that is different from Trino 30 - Used for *most* DDL, with some exceptions that get routed to the Trino engine instead: 31 - CREATE [EXTERNAL] TABLE (without AS SELECT) 32 - ALTER 33 - DROP 34 35 Trino: 36 - Uses double quotes to quote identifiers 37 - Used for DDL operations that involve SELECT queries, eg: 38 - CREATE VIEW / DROP VIEW 39 - CREATE TABLE... AS SELECT 40 - Used for DML operations 41 - SELECT, INSERT, UPDATE, DELETE, MERGE 42 43 The SQLGlot Athena dialect tries to identify which engine a query would be routed to and then uses the 44 tokenizer / parser / generator for that engine. This is unfortunately necessary, as there are certain 45 incompatibilities between the engines' dialects and thus can't be handled by a single, unifying dialect. 46 47 References: 48 - https://docs.aws.amazon.com/athena/latest/ug/ddl-reference.html 49 - https://docs.aws.amazon.com/athena/latest/ug/dml-queries-functions-operators.html 50 """ 51 52 # This Tokenizer consumes a combination of HiveQL and Trino SQL and then processes the tokens 53 # to disambiguate which dialect needs to be actually used in order to tokenize correctly. 54 class Tokenizer(tokens.Tokenizer): 55 IDENTIFIERS = Trino.Tokenizer.IDENTIFIERS + Hive.Tokenizer.IDENTIFIERS 56 STRING_ESCAPES = Trino.Tokenizer.STRING_ESCAPES + Hive.Tokenizer.STRING_ESCAPES 57 HEX_STRINGS = Trino.Tokenizer.HEX_STRINGS + Hive.Tokenizer.HEX_STRINGS 58 UNICODE_STRINGS = Trino.Tokenizer.UNICODE_STRINGS + Hive.Tokenizer.UNICODE_STRINGS 59 60 NUMERIC_LITERALS = { 61 **Trino.Tokenizer.NUMERIC_LITERALS, 62 **Hive.Tokenizer.NUMERIC_LITERALS, 63 } 64 65 KEYWORDS = { 66 **Hive.Tokenizer.KEYWORDS, 67 **Trino.Tokenizer.KEYWORDS, 68 "UNLOAD": TokenType.COMMAND, 69 } 70 71 def __init__(self, dialect: DialectType = None) -> None: 72 super().__init__(dialect=dialect) 73 74 self._hive_tokenizer = Hive().tokenizer() 75 self._trino_tokenizer = _TrinoTokenizer(Trino()) 76 77 def tokenize(self, sql: str) -> list[Token]: 78 # DML only needs Trino's tokenizer; possible Hive statements need disambiguation. 79 try: 80 trino_tokens: list[Token] | None = self._trino_tokenizer.tokenize(sql) 81 except TokenError: 82 trino_tokens = None 83 84 if trino_tokens is not None and not _may_be_hive(trino_tokens): 85 return trino_tokens 86 87 try: 88 tokens = super().tokenize(sql) 89 except TokenError: 90 # Combined rules can reject literals valid in only one engine. 91 if trino_tokens is not None and not _tokenize_as_hive(trino_tokens): 92 return trino_tokens 93 94 try: 95 tokens = self._hive_tokenizer.tokenize(sql) 96 except TokenError: 97 pass 98 else: 99 if _tokenize_as_hive(tokens): 100 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + tokens 101 raise 102 103 if _tokenize_as_hive(tokens): 104 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + self._hive_tokenizer.tokenize(sql) 105 106 return trino_tokens if trino_tokens is not None else self._trino_tokenizer.tokenize(sql) 107 108 Parser = AthenaParser 109 110 Generator = AthenaGenerator 111 112 113_MAYBE_HIVE_TOKENS = { 114 TokenType.ALTER, 115 TokenType.CREATE, 116 TokenType.DESCRIBE, 117 TokenType.DROP, 118 TokenType.SHOW, 119} 120 121 122def _may_be_hive(tokens: list[Token]) -> bool: 123 return bool(tokens) and ( 124 tokens[0].token_type in _MAYBE_HIVE_TOKENS or tokens[0].text.upper() == "MSCK" 125 ) 126 127 128def _tokenize_as_hive(tokens: list[Token]) -> bool: 129 if len(tokens) < 2: 130 return False 131 132 first, second, *rest = tokens 133 134 first_type = first.token_type 135 first_text = first.text.upper() 136 second_type = second.token_type 137 second_text = second.text.upper() 138 139 if first_type in (TokenType.DESCRIBE, TokenType.SHOW) or first_text == "MSCK REPAIR": 140 return True 141 142 if first_type in (TokenType.ALTER, TokenType.CREATE, TokenType.DROP): 143 if second_text in ("DATABASE", "EXTERNAL", "SCHEMA"): 144 return True 145 if second_type == TokenType.VIEW: 146 return False 147 148 return all(t.token_type != TokenType.SELECT for t in rest) 149 150 return False 151 152 153# Athena extensions to Trino's tokenizer 154class _TrinoTokenizer(Trino.Tokenizer): 155 KEYWORDS = { 156 **Trino.Tokenizer.KEYWORDS, 157 "UNLOAD": TokenType.COMMAND, 158 }
15class Athena(Trino): 16 """ 17 Over the years, it looks like AWS has taken various execution engines, bolted on AWS-specific 18 modifications and then built the Athena service around them. 19 20 Thus, Athena is not simply hosted Trino, it's more like a router that routes SQL queries to an 21 execution engine depending on the query type. 22 23 As at 2024-09-10, assuming your Athena workgroup is configured to use "Athena engine version 3", 24 the following engines exist: 25 26 Hive: 27 - Accepts mostly the same syntax as Hadoop / Hive 28 - Uses backticks to quote identifiers 29 - Has a distinctive DDL syntax (around things like setting table properties, storage locations etc) 30 that is different from Trino 31 - Used for *most* DDL, with some exceptions that get routed to the Trino engine instead: 32 - CREATE [EXTERNAL] TABLE (without AS SELECT) 33 - ALTER 34 - DROP 35 36 Trino: 37 - Uses double quotes to quote identifiers 38 - Used for DDL operations that involve SELECT queries, eg: 39 - CREATE VIEW / DROP VIEW 40 - CREATE TABLE... AS SELECT 41 - Used for DML operations 42 - SELECT, INSERT, UPDATE, DELETE, MERGE 43 44 The SQLGlot Athena dialect tries to identify which engine a query would be routed to and then uses the 45 tokenizer / parser / generator for that engine. This is unfortunately necessary, as there are certain 46 incompatibilities between the engines' dialects and thus can't be handled by a single, unifying dialect. 47 48 References: 49 - https://docs.aws.amazon.com/athena/latest/ug/ddl-reference.html 50 - https://docs.aws.amazon.com/athena/latest/ug/dml-queries-functions-operators.html 51 """ 52 53 # This Tokenizer consumes a combination of HiveQL and Trino SQL and then processes the tokens 54 # to disambiguate which dialect needs to be actually used in order to tokenize correctly. 55 class Tokenizer(tokens.Tokenizer): 56 IDENTIFIERS = Trino.Tokenizer.IDENTIFIERS + Hive.Tokenizer.IDENTIFIERS 57 STRING_ESCAPES = Trino.Tokenizer.STRING_ESCAPES + Hive.Tokenizer.STRING_ESCAPES 58 HEX_STRINGS = Trino.Tokenizer.HEX_STRINGS + Hive.Tokenizer.HEX_STRINGS 59 UNICODE_STRINGS = Trino.Tokenizer.UNICODE_STRINGS + Hive.Tokenizer.UNICODE_STRINGS 60 61 NUMERIC_LITERALS = { 62 **Trino.Tokenizer.NUMERIC_LITERALS, 63 **Hive.Tokenizer.NUMERIC_LITERALS, 64 } 65 66 KEYWORDS = { 67 **Hive.Tokenizer.KEYWORDS, 68 **Trino.Tokenizer.KEYWORDS, 69 "UNLOAD": TokenType.COMMAND, 70 } 71 72 def __init__(self, dialect: DialectType = None) -> None: 73 super().__init__(dialect=dialect) 74 75 self._hive_tokenizer = Hive().tokenizer() 76 self._trino_tokenizer = _TrinoTokenizer(Trino()) 77 78 def tokenize(self, sql: str) -> list[Token]: 79 # DML only needs Trino's tokenizer; possible Hive statements need disambiguation. 80 try: 81 trino_tokens: list[Token] | None = self._trino_tokenizer.tokenize(sql) 82 except TokenError: 83 trino_tokens = None 84 85 if trino_tokens is not None and not _may_be_hive(trino_tokens): 86 return trino_tokens 87 88 try: 89 tokens = super().tokenize(sql) 90 except TokenError: 91 # Combined rules can reject literals valid in only one engine. 92 if trino_tokens is not None and not _tokenize_as_hive(trino_tokens): 93 return trino_tokens 94 95 try: 96 tokens = self._hive_tokenizer.tokenize(sql) 97 except TokenError: 98 pass 99 else: 100 if _tokenize_as_hive(tokens): 101 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + tokens 102 raise 103 104 if _tokenize_as_hive(tokens): 105 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + self._hive_tokenizer.tokenize(sql) 106 107 return trino_tokens if trino_tokens is not None else self._trino_tokenizer.tokenize(sql) 108 109 Parser = AthenaParser 110 111 Generator = AthenaGenerator
Over the years, it looks like AWS has taken various execution engines, bolted on AWS-specific modifications and then built the Athena service around them.
Thus, Athena is not simply hosted Trino, it's more like a router that routes SQL queries to an execution engine depending on the query type.
As at 2024-09-10, assuming your Athena workgroup is configured to use "Athena engine version 3", the following engines exist:
Hive:
- Accepts mostly the same syntax as Hadoop / Hive
- Uses backticks to quote identifiers
- Has a distinctive DDL syntax (around things like setting table properties, storage locations etc) that is different from Trino
- Used for most DDL, with some exceptions that get routed to the Trino engine instead:
- CREATE [EXTERNAL] TABLE (without AS SELECT)
- ALTER
- DROP
Trino:
- Uses double quotes to quote identifiers
- Used for DDL operations that involve SELECT queries, eg:
- CREATE VIEW / DROP VIEW
- CREATE TABLE... AS SELECT
- Used for DML operations
- SELECT, INSERT, UPDATE, DELETE, MERGE
The SQLGlot Athena dialect tries to identify which engine a query would be routed to and then uses the tokenizer / parser / generator for that engine. This is unfortunately necessary, as there are certain incompatibilities between the engines' dialects and thus can't be handled by a single, unifying dialect.
References:
Mapping of an escaped sequence (\n) to its unescaped version (
).
Whether string literals support escape sequences (e.g. \n). Set by the metaclass based on the tokenizer's STRING_ESCAPES.
Whether byte string literals support escape sequences. Set by the metaclass based on the tokenizer's BYTE_STRING_ESCAPES.
Mapping of an identifier escape char (\) to its escaped version (\\). Set by the metaclass based on the tokenizer's IDENTIFIER_ESCAPES.
Inherited Members
- sqlglot.dialects.presto.Presto
- INDEX_OFFSET
- NULL_ORDERING
- TIME_FORMAT
- STRICT_STRING_CONCAT
- TYPED_DIVISION
- TABLESAMPLE_SIZE_IS_PERCENT
- SUPPORTS_LIMIT_ALL
- SUPPORTS_VALUES_DEFAULT
- LEAST_GREATEST_IGNORES_NULLS
- UUID_IS_STRING_TYPE
- TIME_MAPPING
- NORMALIZATION_STRATEGY
- EXPRESSION_METADATA
- SUPPORTED_SETTINGS
55 class Tokenizer(tokens.Tokenizer): 56 IDENTIFIERS = Trino.Tokenizer.IDENTIFIERS + Hive.Tokenizer.IDENTIFIERS 57 STRING_ESCAPES = Trino.Tokenizer.STRING_ESCAPES + Hive.Tokenizer.STRING_ESCAPES 58 HEX_STRINGS = Trino.Tokenizer.HEX_STRINGS + Hive.Tokenizer.HEX_STRINGS 59 UNICODE_STRINGS = Trino.Tokenizer.UNICODE_STRINGS + Hive.Tokenizer.UNICODE_STRINGS 60 61 NUMERIC_LITERALS = { 62 **Trino.Tokenizer.NUMERIC_LITERALS, 63 **Hive.Tokenizer.NUMERIC_LITERALS, 64 } 65 66 KEYWORDS = { 67 **Hive.Tokenizer.KEYWORDS, 68 **Trino.Tokenizer.KEYWORDS, 69 "UNLOAD": TokenType.COMMAND, 70 } 71 72 def __init__(self, dialect: DialectType = None) -> None: 73 super().__init__(dialect=dialect) 74 75 self._hive_tokenizer = Hive().tokenizer() 76 self._trino_tokenizer = _TrinoTokenizer(Trino()) 77 78 def tokenize(self, sql: str) -> list[Token]: 79 # DML only needs Trino's tokenizer; possible Hive statements need disambiguation. 80 try: 81 trino_tokens: list[Token] | None = self._trino_tokenizer.tokenize(sql) 82 except TokenError: 83 trino_tokens = None 84 85 if trino_tokens is not None and not _may_be_hive(trino_tokens): 86 return trino_tokens 87 88 try: 89 tokens = super().tokenize(sql) 90 except TokenError: 91 # Combined rules can reject literals valid in only one engine. 92 if trino_tokens is not None and not _tokenize_as_hive(trino_tokens): 93 return trino_tokens 94 95 try: 96 tokens = self._hive_tokenizer.tokenize(sql) 97 except TokenError: 98 pass 99 else: 100 if _tokenize_as_hive(tokens): 101 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + tokens 102 raise 103 104 if _tokenize_as_hive(tokens): 105 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + self._hive_tokenizer.tokenize(sql) 106 107 return trino_tokens if trino_tokens is not None else self._trino_tokenizer.tokenize(sql)
78 def tokenize(self, sql: str) -> list[Token]: 79 # DML only needs Trino's tokenizer; possible Hive statements need disambiguation. 80 try: 81 trino_tokens: list[Token] | None = self._trino_tokenizer.tokenize(sql) 82 except TokenError: 83 trino_tokens = None 84 85 if trino_tokens is not None and not _may_be_hive(trino_tokens): 86 return trino_tokens 87 88 try: 89 tokens = super().tokenize(sql) 90 except TokenError: 91 # Combined rules can reject literals valid in only one engine. 92 if trino_tokens is not None and not _tokenize_as_hive(trino_tokens): 93 return trino_tokens 94 95 try: 96 tokens = self._hive_tokenizer.tokenize(sql) 97 except TokenError: 98 pass 99 else: 100 if _tokenize_as_hive(tokens): 101 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + tokens 102 raise 103 104 if _tokenize_as_hive(tokens): 105 return [Token(TokenType.HIVE_TOKEN_STREAM, "")] + self._hive_tokenizer.tokenize(sql) 106 107 return trino_tokens if trino_tokens is not None else self._trino_tokenizer.tokenize(sql)
Returns a list of tokens corresponding to the SQL string sql.
Inherited Members
- sqlglot.tokens.Tokenizer
- SINGLE_TOKENS
- BIT_STRINGS
- BYTE_STRINGS
- NATIONAL_RAW_STRINGS
- RAW_STRINGS
- HEREDOC_STRINGS
- QUOTES
- VAR_SINGLE_TOKENS
- IDENTIFIER_ESCAPES
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- NUMERIC_ESCAPES
- DROP_UNKNOWN_ESCAPES
- NUMERIC_ESCAPES_ARE_BYTES
- LONE_SURROGATE_REPLACEMENT
- NESTED_COMMENTS
- DASH_COMMENT_REQUIRES_BOUNDARY
- COMMENTS_TERMINATE_AT_NEWLINE_ONLY
- HINT_START
- TOKENS_PRECEDING_HINT
- COMMANDS
- COMMAND_PREFIX_TOKENS
- NUMBERS_CAN_HAVE_DECIMALS
- COMMENTS
- dialect
- sql
- size
- tokens