sqlglot.dialects.duckdb
1from __future__ import annotations 2 3 4from sqlglot import exp, tokens 5 6from sqlglot.dialects.dialect import ( 7 Dialect, 8 NormalizationStrategy, 9 UsingColumnOrder, 10) 11from sqlglot.generators.duckdb import DuckDBGenerator, WS_CONTROL_CHARS_TO_DUCK # noqa: F401 12from sqlglot.parsers.duckdb import DuckDBParser 13from sqlglot.tokens import TokenType 14from sqlglot.typing.duckdb import EXPRESSION_METADATA 15 16 17class DuckDB(Dialect): 18 NULL_ORDERING = "nulls_are_last" 19 SUPPORTS_USER_DEFINED_TYPES = True 20 INDEX_OFFSET = 1 21 CONCAT_COALESCE = True 22 CONCAT_WS_COALESCE = True 23 SUPPORTS_ORDER_BY_ALL = True 24 SUPPORTS_LIMIT_ALL = True 25 SUPPORTS_FIXED_SIZE_ARRAYS = True 26 TABLES_REFERENCEABLE_AS_COLUMNS = True 27 QUERY_RESULTS_ARE_STRUCTS = True 28 STRICT_JSON_PATH_SYNTAX = False 29 NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True 30 UUID_IS_STRING_TYPE = False 31 USING_COLUMN_ORDER = UsingColumnOrder.IN_PLACE 32 33 # https://duckdb.org/docs/sql/introduction.html#creating-a-new-table 34 NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE 35 ASCII_ONLY_NORMALIZATION = True 36 37 DATE_PART_MAPPING = { 38 **Dialect.DATE_PART_MAPPING, 39 "DAYOFWEEKISO": "ISODOW", 40 } 41 42 EXPRESSION_METADATA = EXPRESSION_METADATA.copy() 43 44 DATE_PART_MAPPING.pop("WEEKDAY") 45 46 INVERSE_TIME_MAPPING = { 47 "%e": "%-d", # BigQuery's space-padded day (%e) -> DuckDB's no-padding day (%-d) 48 "%:z": "%z", # In DuckDB %z can represent +/-HH:MM, +/-HHMM, or +/-HH. 49 "%-z": "%z", 50 "%f_zero": "%n", 51 "%f_one": "%n", 52 "%f_two": "%n", 53 "%f_three": "%g", 54 "%f_four": "%n", 55 "%f_five": "%n", 56 "%f_seven": "%n", 57 "%f_eight": "%n", 58 "%f_nine": "%n", 59 } 60 61 def to_json_path(self, path: exp.Expr | None) -> exp.Expr | None: 62 if isinstance(path, exp.Literal): 63 # DuckDB also supports the JSON pointer syntax, where every path starts with a `/`. 64 # Additionally, it allows accessing the back of lists using the `[#-i]` syntax. 65 # This check ensures we'll avoid trying to parse these as JSON paths, which can 66 # either result in a noisy warning or in an invalid representation of the path. 67 path_text = path.name 68 if path_text.startswith("/") or "[#" in path_text: 69 return path 70 71 return super().to_json_path(path) 72 73 UNESCAPED_SEQUENCES = {"\\a": "a", "\\v": "v"} 74 75 class Tokenizer(tokens.Tokenizer): 76 NUMERIC_ESCAPES = {"x": (16, 1, 2, 0xFF), "0": (8, 1, 3, 0o777)} 77 NUMERIC_ESCAPES_ARE_BYTES = True 78 DROP_UNKNOWN_ESCAPES = True 79 BYTE_STRINGS = [("e'", "'"), ("E'", "'")] 80 BYTE_STRING_ESCAPES = ["'", "\\"] 81 HEREDOC_STRINGS = ["$"] 82 83 HEREDOC_TAG_IS_IDENTIFIER = True 84 HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER 85 86 KEYWORDS = { 87 **tokens.Tokenizer.KEYWORDS, 88 "//": TokenType.DIV, 89 "**": TokenType.DSTAR, 90 "^@": TokenType.CARET_AT, 91 "@>": TokenType.AT_GT, 92 "<@": TokenType.LT_AT, 93 "ATTACH": TokenType.ATTACH, 94 "BINARY": TokenType.VARBINARY, 95 "BITSTRING": TokenType.BIT, 96 "BPCHAR": TokenType.TEXT, 97 "CHAR": TokenType.TEXT, 98 "CONNECT": TokenType.CONNECT, 99 "DATETIME": TokenType.TIMESTAMPNTZ, 100 "DETACH": TokenType.DETACH, 101 "DISCONNECT": TokenType.DISCONNECT, 102 "FORCE": TokenType.FORCE, 103 "INSTALL": TokenType.INSTALL, 104 "INT8": TokenType.BIGINT, 105 "LOGICAL": TokenType.BOOLEAN, 106 "MACRO": TokenType.FUNCTION, 107 "ONLY": TokenType.ONLY, 108 "PIVOT_WIDER": TokenType.PIVOT, 109 "POSITIONAL": TokenType.POSITIONAL, 110 "RESET": TokenType.COMMAND, 111 "ROW": TokenType.STRUCT, 112 "SIGNED": TokenType.INT, 113 "STRING": TokenType.TEXT, 114 "SUMMARIZE": TokenType.SUMMARIZE, 115 "TIMESTAMP": TokenType.TIMESTAMPNTZ, 116 "TIMESTAMP_S": TokenType.TIMESTAMP_S, 117 "TIMESTAMP_MS": TokenType.TIMESTAMP_MS, 118 "TIMESTAMP_NS": TokenType.TIMESTAMP_NS, 119 "TIMESTAMP_US": TokenType.TIMESTAMP, 120 "UBIGINT": TokenType.UBIGINT, 121 "UINTEGER": TokenType.UINT, 122 "USMALLINT": TokenType.USMALLINT, 123 "UTINYINT": TokenType.UTINYINT, 124 "VARCHAR": TokenType.TEXT, 125 } 126 KEYWORDS.pop("/*+") 127 KEYWORDS.pop("SORT BY") 128 129 SINGLE_TOKENS = { 130 **tokens.Tokenizer.SINGLE_TOKENS, 131 "$": TokenType.PARAMETER, 132 } 133 134 VAR_SINGLE_TOKENS = {"$"} 135 136 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 137 138 Parser = DuckDBParser 139 140 Generator = DuckDBGenerator
18class DuckDB(Dialect): 19 NULL_ORDERING = "nulls_are_last" 20 SUPPORTS_USER_DEFINED_TYPES = True 21 INDEX_OFFSET = 1 22 CONCAT_COALESCE = True 23 CONCAT_WS_COALESCE = True 24 SUPPORTS_ORDER_BY_ALL = True 25 SUPPORTS_LIMIT_ALL = True 26 SUPPORTS_FIXED_SIZE_ARRAYS = True 27 TABLES_REFERENCEABLE_AS_COLUMNS = True 28 QUERY_RESULTS_ARE_STRUCTS = True 29 STRICT_JSON_PATH_SYNTAX = False 30 NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True 31 UUID_IS_STRING_TYPE = False 32 USING_COLUMN_ORDER = UsingColumnOrder.IN_PLACE 33 34 # https://duckdb.org/docs/sql/introduction.html#creating-a-new-table 35 NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE 36 ASCII_ONLY_NORMALIZATION = True 37 38 DATE_PART_MAPPING = { 39 **Dialect.DATE_PART_MAPPING, 40 "DAYOFWEEKISO": "ISODOW", 41 } 42 43 EXPRESSION_METADATA = EXPRESSION_METADATA.copy() 44 45 DATE_PART_MAPPING.pop("WEEKDAY") 46 47 INVERSE_TIME_MAPPING = { 48 "%e": "%-d", # BigQuery's space-padded day (%e) -> DuckDB's no-padding day (%-d) 49 "%:z": "%z", # In DuckDB %z can represent +/-HH:MM, +/-HHMM, or +/-HH. 50 "%-z": "%z", 51 "%f_zero": "%n", 52 "%f_one": "%n", 53 "%f_two": "%n", 54 "%f_three": "%g", 55 "%f_four": "%n", 56 "%f_five": "%n", 57 "%f_seven": "%n", 58 "%f_eight": "%n", 59 "%f_nine": "%n", 60 } 61 62 def to_json_path(self, path: exp.Expr | None) -> exp.Expr | None: 63 if isinstance(path, exp.Literal): 64 # DuckDB also supports the JSON pointer syntax, where every path starts with a `/`. 65 # Additionally, it allows accessing the back of lists using the `[#-i]` syntax. 66 # This check ensures we'll avoid trying to parse these as JSON paths, which can 67 # either result in a noisy warning or in an invalid representation of the path. 68 path_text = path.name 69 if path_text.startswith("/") or "[#" in path_text: 70 return path 71 72 return super().to_json_path(path) 73 74 UNESCAPED_SEQUENCES = {"\\a": "a", "\\v": "v"} 75 76 class Tokenizer(tokens.Tokenizer): 77 NUMERIC_ESCAPES = {"x": (16, 1, 2, 0xFF), "0": (8, 1, 3, 0o777)} 78 NUMERIC_ESCAPES_ARE_BYTES = True 79 DROP_UNKNOWN_ESCAPES = True 80 BYTE_STRINGS = [("e'", "'"), ("E'", "'")] 81 BYTE_STRING_ESCAPES = ["'", "\\"] 82 HEREDOC_STRINGS = ["$"] 83 84 HEREDOC_TAG_IS_IDENTIFIER = True 85 HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER 86 87 KEYWORDS = { 88 **tokens.Tokenizer.KEYWORDS, 89 "//": TokenType.DIV, 90 "**": TokenType.DSTAR, 91 "^@": TokenType.CARET_AT, 92 "@>": TokenType.AT_GT, 93 "<@": TokenType.LT_AT, 94 "ATTACH": TokenType.ATTACH, 95 "BINARY": TokenType.VARBINARY, 96 "BITSTRING": TokenType.BIT, 97 "BPCHAR": TokenType.TEXT, 98 "CHAR": TokenType.TEXT, 99 "CONNECT": TokenType.CONNECT, 100 "DATETIME": TokenType.TIMESTAMPNTZ, 101 "DETACH": TokenType.DETACH, 102 "DISCONNECT": TokenType.DISCONNECT, 103 "FORCE": TokenType.FORCE, 104 "INSTALL": TokenType.INSTALL, 105 "INT8": TokenType.BIGINT, 106 "LOGICAL": TokenType.BOOLEAN, 107 "MACRO": TokenType.FUNCTION, 108 "ONLY": TokenType.ONLY, 109 "PIVOT_WIDER": TokenType.PIVOT, 110 "POSITIONAL": TokenType.POSITIONAL, 111 "RESET": TokenType.COMMAND, 112 "ROW": TokenType.STRUCT, 113 "SIGNED": TokenType.INT, 114 "STRING": TokenType.TEXT, 115 "SUMMARIZE": TokenType.SUMMARIZE, 116 "TIMESTAMP": TokenType.TIMESTAMPNTZ, 117 "TIMESTAMP_S": TokenType.TIMESTAMP_S, 118 "TIMESTAMP_MS": TokenType.TIMESTAMP_MS, 119 "TIMESTAMP_NS": TokenType.TIMESTAMP_NS, 120 "TIMESTAMP_US": TokenType.TIMESTAMP, 121 "UBIGINT": TokenType.UBIGINT, 122 "UINTEGER": TokenType.UINT, 123 "USMALLINT": TokenType.USMALLINT, 124 "UTINYINT": TokenType.UTINYINT, 125 "VARCHAR": TokenType.TEXT, 126 } 127 KEYWORDS.pop("/*+") 128 KEYWORDS.pop("SORT BY") 129 130 SINGLE_TOKENS = { 131 **tokens.Tokenizer.SINGLE_TOKENS, 132 "$": TokenType.PARAMETER, 133 } 134 135 VAR_SINGLE_TOKENS = {"$"} 136 137 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 138 139 Parser = DuckDBParser 140 141 Generator = DuckDBGenerator
Default NULL ordering method to use if not explicitly set.
Possible values: "nulls_are_small", "nulls_are_large", "nulls_are_last"
A NULL arg in CONCAT yields NULL by default, but in some dialects it yields an empty string.
A NULL arg in CONCAT_WS yields NULL by default, but in some dialects it is skipped.
Whether ORDER BY ALL is supported (expands to all the selected columns) as in DuckDB, Spark3/Databricks
Whether expressions such as x::INT[5] should be parsed as fixed-size array defs/casts e.g. in DuckDB. In dialects which don't support fixed size arrays such as Snowflake, this should be interpreted as a subscript/index operator.
Whether table names can be referenced as columns (treated as structs).
BigQuery allows tables to be referenced as columns in queries, automatically treating them as struct values containing all the table's columns.
For example, in BigQuery: SELECT t FROM my_table AS t -- Returns entire row as a struct
Whether query results are typed as structs in metadata for type inference.
In BigQuery, subqueries store their column types as a STRUCT in metadata,
enabling special type inference for ARRAY(SELECT ...) expressions:
ARRAY(SELECT x, y FROM t) → ARRAY For single column subqueries, BigQuery unwraps the struct:
ARRAY(SELECT x FROM t) → ARRAY This is metadata-only for type inference.
Whether failing to parse a JSON path expression using the JSONPath dialect will log a warning.
Whether number literals can include underscores for better readability
Where star expansion places the columns of a USING or NATURAL join.
Given a(a_id, k1, k2) and b(k2, b_id, k1), SELECT * FROM a JOIN b USING (k2, k1) returns:
USING_LIST:k2, k1, a_id, b_idLEFT_TABLE:k1, k2, a_id, b_idIN_PLACE:a_id, k1, k2, b_id
When join columns come first, this applies at every join: each USING join moves its columns ahead of all the columns to its left.
Specifies the strategy according to which identifiers should be normalized.
Whether identifiers are only normalized with respect to ASCII characters, e.g. Ä and
ä are different identifiers in DuckDB, but the same identifier in Spark.
62 def to_json_path(self, path: exp.Expr | None) -> exp.Expr | None: 63 if isinstance(path, exp.Literal): 64 # DuckDB also supports the JSON pointer syntax, where every path starts with a `/`. 65 # Additionally, it allows accessing the back of lists using the `[#-i]` syntax. 66 # This check ensures we'll avoid trying to parse these as JSON paths, which can 67 # either result in a noisy warning or in an invalid representation of the path. 68 path_text = path.name 69 if path_text.startswith("/") or "[#" in path_text: 70 return path 71 72 return super().to_json_path(path)
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.
76 class Tokenizer(tokens.Tokenizer): 77 NUMERIC_ESCAPES = {"x": (16, 1, 2, 0xFF), "0": (8, 1, 3, 0o777)} 78 NUMERIC_ESCAPES_ARE_BYTES = True 79 DROP_UNKNOWN_ESCAPES = True 80 BYTE_STRINGS = [("e'", "'"), ("E'", "'")] 81 BYTE_STRING_ESCAPES = ["'", "\\"] 82 HEREDOC_STRINGS = ["$"] 83 84 HEREDOC_TAG_IS_IDENTIFIER = True 85 HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER 86 87 KEYWORDS = { 88 **tokens.Tokenizer.KEYWORDS, 89 "//": TokenType.DIV, 90 "**": TokenType.DSTAR, 91 "^@": TokenType.CARET_AT, 92 "@>": TokenType.AT_GT, 93 "<@": TokenType.LT_AT, 94 "ATTACH": TokenType.ATTACH, 95 "BINARY": TokenType.VARBINARY, 96 "BITSTRING": TokenType.BIT, 97 "BPCHAR": TokenType.TEXT, 98 "CHAR": TokenType.TEXT, 99 "CONNECT": TokenType.CONNECT, 100 "DATETIME": TokenType.TIMESTAMPNTZ, 101 "DETACH": TokenType.DETACH, 102 "DISCONNECT": TokenType.DISCONNECT, 103 "FORCE": TokenType.FORCE, 104 "INSTALL": TokenType.INSTALL, 105 "INT8": TokenType.BIGINT, 106 "LOGICAL": TokenType.BOOLEAN, 107 "MACRO": TokenType.FUNCTION, 108 "ONLY": TokenType.ONLY, 109 "PIVOT_WIDER": TokenType.PIVOT, 110 "POSITIONAL": TokenType.POSITIONAL, 111 "RESET": TokenType.COMMAND, 112 "ROW": TokenType.STRUCT, 113 "SIGNED": TokenType.INT, 114 "STRING": TokenType.TEXT, 115 "SUMMARIZE": TokenType.SUMMARIZE, 116 "TIMESTAMP": TokenType.TIMESTAMPNTZ, 117 "TIMESTAMP_S": TokenType.TIMESTAMP_S, 118 "TIMESTAMP_MS": TokenType.TIMESTAMP_MS, 119 "TIMESTAMP_NS": TokenType.TIMESTAMP_NS, 120 "TIMESTAMP_US": TokenType.TIMESTAMP, 121 "UBIGINT": TokenType.UBIGINT, 122 "UINTEGER": TokenType.UINT, 123 "USMALLINT": TokenType.USMALLINT, 124 "UTINYINT": TokenType.UTINYINT, 125 "VARCHAR": TokenType.TEXT, 126 } 127 KEYWORDS.pop("/*+") 128 KEYWORDS.pop("SORT BY") 129 130 SINGLE_TOKENS = { 131 **tokens.Tokenizer.SINGLE_TOKENS, 132 "$": TokenType.PARAMETER, 133 } 134 135 VAR_SINGLE_TOKENS = {"$"} 136 137 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- BIT_STRINGS
- HEX_STRINGS
- NATIONAL_RAW_STRINGS
- RAW_STRINGS
- UNICODE_STRINGS
- IDENTIFIERS
- QUOTES
- STRING_ESCAPES
- IDENTIFIER_ESCAPES
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- LONE_SURROGATE_REPLACEMENT
- NESTED_COMMENTS
- DASH_COMMENT_REQUIRES_BOUNDARY
- COMMENTS_TERMINATE_AT_NEWLINE_ONLY
- HINT_START
- TOKENS_PRECEDING_HINT
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- NUMBERS_CAN_HAVE_DECIMALS
- COMMENTS
- dialect
- tokenize
- sql
- size
- tokens