sqlglot.dialects.clickhouse
1from __future__ import annotations 2 3 4from sqlglot import exp, tokens 5from sqlglot.dialects.dialect import ( 6 Dialect, 7 NormalizationStrategy, 8) 9from sqlglot.generators.clickhouse import ClickHouseGenerator 10from sqlglot.parsers.clickhouse import ClickHouseParser 11from sqlglot.tokens import TokenType 12from sqlglot.typing.clickhouse import EXPRESSION_METADATA 13 14 15class ClickHouse(Dialect): 16 INDEX_OFFSET = 1 17 NORMALIZE_FUNCTIONS: bool | str = False 18 NULL_ORDERING = "nulls_are_last" 19 SUPPORTS_USER_DEFINED_TYPES = False 20 LOG_BASE_FIRST: bool | None = None 21 FORCE_EARLY_ALIAS_REF_EXPANSION = True 22 ORIGINAL_NAME_META_KEY = "clickhouse_name" 23 NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True 24 IDENTIFIERS_CAN_START_WITH_DIGIT = True 25 HEX_STRING_IS_INTEGER_TYPE = True 26 27 # https://github.com/ClickHouse/ClickHouse/issues/33935#issue-1112165779 28 NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE 29 30 EXPRESSION_METADATA = EXPRESSION_METADATA.copy() 31 32 UNESCAPED_SEQUENCES = { 33 "\\0": "\0", 34 "\\e": "\x1b", 35 "\\N": "", 36 "\\/": "/", 37 '\\"': '"', 38 "\\=": "=", 39 "\\`": "`", 40 **{"\\" + chr(i): chr(i) for i in range(1, 32)}, 41 } 42 43 CREATABLE_KIND_MAPPING = {"DATABASE": "SCHEMA"} 44 45 SET_OP_DISTINCT_BY_DEFAULT: dict[type[exp.Expr], bool | None] = { 46 exp.Except: False, 47 exp.Intersect: False, 48 exp.Union: None, 49 } 50 51 def generate_values_aliases(self, expression: exp.Values) -> list[exp.Identifier]: 52 # Clickhouse allows VALUES to have an embedded structure e.g: 53 # VALUES('person String, place String', ('Noah', 'Paris'), ...) 54 # In this case, we don't want to qualify the columns 55 values = expression.expressions[0].expressions 56 57 structure = ( 58 values[0] 59 if (len(values) > 1 and values[0].is_string and isinstance(values[1], exp.Tuple)) 60 else None 61 ) 62 if structure: 63 # Split each column definition into the column name e.g: 64 # 'person String, place String' -> ['person', 'place'] 65 structure_coldefs = [coldef.strip() for coldef in structure.name.split(",")] 66 column_aliases = [ 67 exp.to_identifier(coldef.split(" ")[0]) for coldef in structure_coldefs 68 ] 69 else: 70 # Default column aliases in CH are "c1", "c2", etc. 71 column_aliases = [ 72 exp.to_identifier(f"c{i + 1}") for i in range(len(values[0].expressions)) 73 ] 74 75 return column_aliases 76 77 class Tokenizer(tokens.Tokenizer): 78 NUMERIC_ESCAPES = {"x": (16, 2, 2, 0xFF)} 79 NUMERIC_ESCAPES_ARE_BYTES = True 80 COMMENTS = ["--", "#", "#!", ("/*", "*/")] 81 COMMENTS_TERMINATE_AT_NEWLINE_ONLY = True 82 IDENTIFIERS = ['"', "`"] 83 IDENTIFIER_ESCAPES = ["\\"] 84 STRING_ESCAPES = ["'", "\\"] 85 BIT_STRINGS = [("0b", "")] 86 HEX_STRINGS = [("0x", ""), ("0X", "")] 87 HEREDOC_STRINGS = ["$"] 88 89 KEYWORDS = { 90 **tokens.Tokenizer.KEYWORDS, 91 ".:": TokenType.DOTCOLON, 92 ".^": TokenType.DOTCARET, 93 "ATTACH": TokenType.COMMAND, 94 "DATE32": TokenType.DATE32, 95 "DETACH": TokenType.DETACH, 96 "DATETIME64": TokenType.DATETIME64, 97 "DICTIONARY": TokenType.DICTIONARY, 98 "DYNAMIC": TokenType.DYNAMIC, 99 "ENUM8": TokenType.ENUM8, 100 "ENUM16": TokenType.ENUM16, 101 "EXCHANGE": TokenType.COMMAND, 102 "FINAL": TokenType.FINAL, 103 "FIXEDSTRING": TokenType.FIXEDSTRING, 104 "FLOAT32": TokenType.FLOAT, 105 "FLOAT64": TokenType.DOUBLE, 106 "GLOBAL": TokenType.GLOBAL, 107 "LOWCARDINALITY": TokenType.LOWCARDINALITY, 108 "MAP": TokenType.MAP, 109 "NESTED": TokenType.NESTED, 110 "NOTHING": TokenType.NOTHING, 111 "SAMPLE": TokenType.TABLE_SAMPLE, 112 "TUPLE": TokenType.STRUCT, 113 "UINT16": TokenType.USMALLINT, 114 "UINT32": TokenType.UINT, 115 "UINT64": TokenType.UBIGINT, 116 "UINT8": TokenType.UTINYINT, 117 "IPV4": TokenType.IPV4, 118 "IPV6": TokenType.IPV6, 119 "POINT": TokenType.POINT, 120 "PROJECTION": TokenType.PROJECTION, 121 "RING": TokenType.RING, 122 "LINESTRING": TokenType.LINESTRING, 123 "MULTILINESTRING": TokenType.MULTILINESTRING, 124 "POLYGON": TokenType.POLYGON, 125 "MULTIPOLYGON": TokenType.MULTIPOLYGON, 126 "AGGREGATEFUNCTION": TokenType.AGGREGATEFUNCTION, 127 "SIMPLEAGGREGATEFUNCTION": TokenType.SIMPLEAGGREGATEFUNCTION, 128 "SYSTEM": TokenType.COMMAND, 129 "PREWHERE": TokenType.PREWHERE, 130 } 131 132 KEYWORDS.pop("/*+") 133 134 SINGLE_TOKENS = { 135 **tokens.Tokenizer.SINGLE_TOKENS, 136 "$": TokenType.HEREDOC_STRING, 137 } 138 139 Parser = ClickHouseParser 140 141 Generator = ClickHouseGenerator
16class ClickHouse(Dialect): 17 INDEX_OFFSET = 1 18 NORMALIZE_FUNCTIONS: bool | str = False 19 NULL_ORDERING = "nulls_are_last" 20 SUPPORTS_USER_DEFINED_TYPES = False 21 LOG_BASE_FIRST: bool | None = None 22 FORCE_EARLY_ALIAS_REF_EXPANSION = True 23 ORIGINAL_NAME_META_KEY = "clickhouse_name" 24 NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True 25 IDENTIFIERS_CAN_START_WITH_DIGIT = True 26 HEX_STRING_IS_INTEGER_TYPE = True 27 28 # https://github.com/ClickHouse/ClickHouse/issues/33935#issue-1112165779 29 NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE 30 31 EXPRESSION_METADATA = EXPRESSION_METADATA.copy() 32 33 UNESCAPED_SEQUENCES = { 34 "\\0": "\0", 35 "\\e": "\x1b", 36 "\\N": "", 37 "\\/": "/", 38 '\\"': '"', 39 "\\=": "=", 40 "\\`": "`", 41 **{"\\" + chr(i): chr(i) for i in range(1, 32)}, 42 } 43 44 CREATABLE_KIND_MAPPING = {"DATABASE": "SCHEMA"} 45 46 SET_OP_DISTINCT_BY_DEFAULT: dict[type[exp.Expr], bool | None] = { 47 exp.Except: False, 48 exp.Intersect: False, 49 exp.Union: None, 50 } 51 52 def generate_values_aliases(self, expression: exp.Values) -> list[exp.Identifier]: 53 # Clickhouse allows VALUES to have an embedded structure e.g: 54 # VALUES('person String, place String', ('Noah', 'Paris'), ...) 55 # In this case, we don't want to qualify the columns 56 values = expression.expressions[0].expressions 57 58 structure = ( 59 values[0] 60 if (len(values) > 1 and values[0].is_string and isinstance(values[1], exp.Tuple)) 61 else None 62 ) 63 if structure: 64 # Split each column definition into the column name e.g: 65 # 'person String, place String' -> ['person', 'place'] 66 structure_coldefs = [coldef.strip() for coldef in structure.name.split(",")] 67 column_aliases = [ 68 exp.to_identifier(coldef.split(" ")[0]) for coldef in structure_coldefs 69 ] 70 else: 71 # Default column aliases in CH are "c1", "c2", etc. 72 column_aliases = [ 73 exp.to_identifier(f"c{i + 1}") for i in range(len(values[0].expressions)) 74 ] 75 76 return column_aliases 77 78 class Tokenizer(tokens.Tokenizer): 79 NUMERIC_ESCAPES = {"x": (16, 2, 2, 0xFF)} 80 NUMERIC_ESCAPES_ARE_BYTES = True 81 COMMENTS = ["--", "#", "#!", ("/*", "*/")] 82 COMMENTS_TERMINATE_AT_NEWLINE_ONLY = True 83 IDENTIFIERS = ['"', "`"] 84 IDENTIFIER_ESCAPES = ["\\"] 85 STRING_ESCAPES = ["'", "\\"] 86 BIT_STRINGS = [("0b", "")] 87 HEX_STRINGS = [("0x", ""), ("0X", "")] 88 HEREDOC_STRINGS = ["$"] 89 90 KEYWORDS = { 91 **tokens.Tokenizer.KEYWORDS, 92 ".:": TokenType.DOTCOLON, 93 ".^": TokenType.DOTCARET, 94 "ATTACH": TokenType.COMMAND, 95 "DATE32": TokenType.DATE32, 96 "DETACH": TokenType.DETACH, 97 "DATETIME64": TokenType.DATETIME64, 98 "DICTIONARY": TokenType.DICTIONARY, 99 "DYNAMIC": TokenType.DYNAMIC, 100 "ENUM8": TokenType.ENUM8, 101 "ENUM16": TokenType.ENUM16, 102 "EXCHANGE": TokenType.COMMAND, 103 "FINAL": TokenType.FINAL, 104 "FIXEDSTRING": TokenType.FIXEDSTRING, 105 "FLOAT32": TokenType.FLOAT, 106 "FLOAT64": TokenType.DOUBLE, 107 "GLOBAL": TokenType.GLOBAL, 108 "LOWCARDINALITY": TokenType.LOWCARDINALITY, 109 "MAP": TokenType.MAP, 110 "NESTED": TokenType.NESTED, 111 "NOTHING": TokenType.NOTHING, 112 "SAMPLE": TokenType.TABLE_SAMPLE, 113 "TUPLE": TokenType.STRUCT, 114 "UINT16": TokenType.USMALLINT, 115 "UINT32": TokenType.UINT, 116 "UINT64": TokenType.UBIGINT, 117 "UINT8": TokenType.UTINYINT, 118 "IPV4": TokenType.IPV4, 119 "IPV6": TokenType.IPV6, 120 "POINT": TokenType.POINT, 121 "PROJECTION": TokenType.PROJECTION, 122 "RING": TokenType.RING, 123 "LINESTRING": TokenType.LINESTRING, 124 "MULTILINESTRING": TokenType.MULTILINESTRING, 125 "POLYGON": TokenType.POLYGON, 126 "MULTIPOLYGON": TokenType.MULTIPOLYGON, 127 "AGGREGATEFUNCTION": TokenType.AGGREGATEFUNCTION, 128 "SIMPLEAGGREGATEFUNCTION": TokenType.SIMPLEAGGREGATEFUNCTION, 129 "SYSTEM": TokenType.COMMAND, 130 "PREWHERE": TokenType.PREWHERE, 131 } 132 133 KEYWORDS.pop("/*+") 134 135 SINGLE_TOKENS = { 136 **tokens.Tokenizer.SINGLE_TOKENS, 137 "$": TokenType.HEREDOC_STRING, 138 } 139 140 Parser = ClickHouseParser 141 142 Generator = ClickHouseGenerator
Determines how function names are going to be normalized.
Possible values:
"upper" or True: Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
Default NULL ordering method to use if not explicitly set.
Possible values: "nulls_are_small", "nulls_are_large", "nulls_are_last"
Whether the base comes first in the LOG function.
Possible values: True, False, None (two arguments are not supported by LOG)
Whether alias reference expansion (_expand_alias_refs()) should run before column qualification (_qualify_columns()).
For example:
WITH data AS ( SELECT 1 AS id, 2 AS my_id ) SELECT id AS my_id FROM data WHERE my_id = 1 GROUP BY my_id, HAVING my_id = 1
In most dialects, "my_id" would refer to "data.my_id" across the query, except: - BigQuery, which will forward the alias to GROUP BY + HAVING clauses i.e it resolves to "WHERE my_id = 1 GROUP BY id HAVING id = 1" - Clickhouse, which will forward the alias across the query i.e it resolves to "WHERE id = 1 GROUP BY id HAVING id = 1"
Dialect-specific metadata key for preserving original function names, or None to disable. Only generators using the same key reuse these names, allowing round trips of aliases that share an AST node, e.g. JSON_VALUE vs JSON_EXTRACT_SCALAR in BigQuery.
Whether number literals can include underscores for better readability
Whether hex strings such as x'CC' evaluate to integer or binary/blob type
Specifies the strategy according to which identifiers should be normalized.
Mapping of an escaped sequence (\n) to its unescaped version (
).
Helper for dialects that use a different name for the same creatable kind. For example, the Clickhouse equivalent of CREATE SCHEMA is CREATE DATABASE.
Whether a set operation uses DISTINCT by default. This is None when either DISTINCT or ALL
must be explicitly specified.
52 def generate_values_aliases(self, expression: exp.Values) -> list[exp.Identifier]: 53 # Clickhouse allows VALUES to have an embedded structure e.g: 54 # VALUES('person String, place String', ('Noah', 'Paris'), ...) 55 # In this case, we don't want to qualify the columns 56 values = expression.expressions[0].expressions 57 58 structure = ( 59 values[0] 60 if (len(values) > 1 and values[0].is_string and isinstance(values[1], exp.Tuple)) 61 else None 62 ) 63 if structure: 64 # Split each column definition into the column name e.g: 65 # 'person String, place String' -> ['person', 'place'] 66 structure_coldefs = [coldef.strip() for coldef in structure.name.split(",")] 67 column_aliases = [ 68 exp.to_identifier(coldef.split(" ")[0]) for coldef in structure_coldefs 69 ] 70 else: 71 # Default column aliases in CH are "c1", "c2", etc. 72 column_aliases = [ 73 exp.to_identifier(f"c{i + 1}") for i in range(len(values[0].expressions)) 74 ] 75 76 return column_aliases
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.
78 class Tokenizer(tokens.Tokenizer): 79 NUMERIC_ESCAPES = {"x": (16, 2, 2, 0xFF)} 80 NUMERIC_ESCAPES_ARE_BYTES = True 81 COMMENTS = ["--", "#", "#!", ("/*", "*/")] 82 COMMENTS_TERMINATE_AT_NEWLINE_ONLY = True 83 IDENTIFIERS = ['"', "`"] 84 IDENTIFIER_ESCAPES = ["\\"] 85 STRING_ESCAPES = ["'", "\\"] 86 BIT_STRINGS = [("0b", "")] 87 HEX_STRINGS = [("0x", ""), ("0X", "")] 88 HEREDOC_STRINGS = ["$"] 89 90 KEYWORDS = { 91 **tokens.Tokenizer.KEYWORDS, 92 ".:": TokenType.DOTCOLON, 93 ".^": TokenType.DOTCARET, 94 "ATTACH": TokenType.COMMAND, 95 "DATE32": TokenType.DATE32, 96 "DETACH": TokenType.DETACH, 97 "DATETIME64": TokenType.DATETIME64, 98 "DICTIONARY": TokenType.DICTIONARY, 99 "DYNAMIC": TokenType.DYNAMIC, 100 "ENUM8": TokenType.ENUM8, 101 "ENUM16": TokenType.ENUM16, 102 "EXCHANGE": TokenType.COMMAND, 103 "FINAL": TokenType.FINAL, 104 "FIXEDSTRING": TokenType.FIXEDSTRING, 105 "FLOAT32": TokenType.FLOAT, 106 "FLOAT64": TokenType.DOUBLE, 107 "GLOBAL": TokenType.GLOBAL, 108 "LOWCARDINALITY": TokenType.LOWCARDINALITY, 109 "MAP": TokenType.MAP, 110 "NESTED": TokenType.NESTED, 111 "NOTHING": TokenType.NOTHING, 112 "SAMPLE": TokenType.TABLE_SAMPLE, 113 "TUPLE": TokenType.STRUCT, 114 "UINT16": TokenType.USMALLINT, 115 "UINT32": TokenType.UINT, 116 "UINT64": TokenType.UBIGINT, 117 "UINT8": TokenType.UTINYINT, 118 "IPV4": TokenType.IPV4, 119 "IPV6": TokenType.IPV6, 120 "POINT": TokenType.POINT, 121 "PROJECTION": TokenType.PROJECTION, 122 "RING": TokenType.RING, 123 "LINESTRING": TokenType.LINESTRING, 124 "MULTILINESTRING": TokenType.MULTILINESTRING, 125 "POLYGON": TokenType.POLYGON, 126 "MULTIPOLYGON": TokenType.MULTIPOLYGON, 127 "AGGREGATEFUNCTION": TokenType.AGGREGATEFUNCTION, 128 "SIMPLEAGGREGATEFUNCTION": TokenType.SIMPLEAGGREGATEFUNCTION, 129 "SYSTEM": TokenType.COMMAND, 130 "PREWHERE": TokenType.PREWHERE, 131 } 132 133 KEYWORDS.pop("/*+") 134 135 SINGLE_TOKENS = { 136 **tokens.Tokenizer.SINGLE_TOKENS, 137 "$": TokenType.HEREDOC_STRING, 138 }
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- BYTE_STRINGS
- RAW_STRINGS
- UNICODE_STRINGS
- QUOTES
- VAR_SINGLE_TOKENS
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- DROP_UNKNOWN_ESCAPES
- LONE_SURROGATE_REPLACEMENT
- NESTED_COMMENTS
- DASH_COMMENT_REQUIRES_BOUNDARY
- HINT_START
- TOKENS_PRECEDING_HINT
- COMMANDS
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- NUMBERS_CAN_HAVE_DECIMALS
- dialect
- tokenize
- sql
- size
- tokens