diff --git a/docs/en/sql-reference/data-types/enum.md b/docs/en/sql-reference/data-types/enum.md index ae22e60a5f3..0f6e1e1f001 100644 --- a/docs/en/sql-reference/data-types/enum.md +++ b/docs/en/sql-reference/data-types/enum.md @@ -7,7 +7,7 @@ toc_title: Enum Enumerated type consisting of named values. -Named values must be declared as `'string' = integer` pairs. ClickHouse stores only numbers, but supports operations with the values through their names. +Named values can be declared as `'string' = integer` pairs or `'string'` names . ClickHouse stores only numbers, but supports operations with the values through their names. ClickHouse supports: @@ -28,6 +28,39 @@ CREATE TABLE t_enum ENGINE = TinyLog ``` +Similarly, you could omit numbers. ClickHouse will assign consecutive numbers automatically. Numbers are assigned starting from 1 by default. + +``` sql +CREATE TABLE t_enum +( + x Enum('hello', 'world') +) +ENGINE = TinyLog +``` + +You can also specify legal starting number for the first name. + +``` sql +CREATE TABLE t_enum +( + x Enum('hello' = 1, 'world') +) +ENGINE = TinyLog +``` + +``` sql +CREATE TABLE t_enum +( + x Enum8('hello' = -129, 'world') +) +ENGINE = TinyLog +``` + +``` text +Exception on server: +Code: 69. DB::Exception: Value -129 for element 'hello' exceeds range of Enum8. +``` + Column `x` can only store values that are listed in the type definition: `'hello'` or `'world'`. If you try to save any other value, ClickHouse will raise an exception. 8-bit size for this `Enum` is chosen automatically. ``` sql diff --git a/docs/ru/sql-reference/data-types/enum.md b/docs/ru/sql-reference/data-types/enum.md index 95c053bed2c..1262c9e1cd5 100644 --- a/docs/ru/sql-reference/data-types/enum.md +++ b/docs/ru/sql-reference/data-types/enum.md @@ -7,7 +7,7 @@ toc_title: Enum Перечисляемый тип данных, содержащий именованные значения. -Именованные значения задаются парами `'string' = integer`. ClickHouse хранит только числа, но допускает операции над ними с помощью заданных имён. +Именованные значения задаются либо парами `'string' = integer`, либо именами `'string'`. ClickHouse хранит только числа, но допускает операции над ними с помощью заданных имён. ClickHouse поддерживает: @@ -28,6 +28,39 @@ CREATE TABLE t_enum ENGINE = TinyLog ``` +Номера могут быть опущены - в этом случае ClickHouse автоматически присвоит последовательные номера, начиная с 1. + +``` sql +CREATE TABLE t_enum +( + x Enum('hello', 'world') +) +ENGINE = TinyLog +``` + +Можно также указать допустимый стартовый номер для первого имени. + +``` sql +CREATE TABLE t_enum +( + x Enum('hello' = 1, 'world') +) +ENGINE = TinyLog +``` + +``` sql +CREATE TABLE t_enum +( + x Enum8('hello' = -129, 'world') +) +ENGINE = TinyLog +``` + +``` text +Exception on server: +Code: 69. DB::Exception: Value -129 for element 'hello' exceeds range of Enum8. +``` + В столбец `x` можно сохранять только значения, перечисленные при определении типа, т.е. `'hello'` или `'world'`. Если вы попытаетесь сохранить любое другое значение, ClickHouse сгенерирует исключение. ClickHouse автоматически выберет размерность 8-bit для этого `Enum`. ``` sql diff --git a/src/DataTypes/DataTypeEnum.cpp b/src/DataTypes/DataTypeEnum.cpp index 9d1424d862b..c58e186b980 100644 --- a/src/DataTypes/DataTypeEnum.cpp +++ b/src/DataTypes/DataTypeEnum.cpp @@ -193,26 +193,46 @@ static void checkASTStructure(const ASTPtr & child) static void autoAssignNumberForEnum(const ASTPtr & arguments) { - UInt64 literal_child_count = 0; - UInt64 func_child_count = 0; + Int64 literal_child_assign_num = 1; ASTs assign_number_child; assign_number_child.reserve(arguments->children.size()); + bool is_first_child = true; + size_t assign_count= 0; + for (const ASTPtr & child : arguments->children) { if (child->as()) { - ASTPtr func = makeASTFunction("equals", child, std::make_shared(++literal_child_count)); + assign_count += !is_first_child; + ASTPtr func = makeASTFunction("equals", child, std::make_shared(literal_child_assign_num + assign_count)); assign_number_child.emplace_back(func); } - else + else if (child->as()) { - ++func_child_count; + if (is_first_child) + { + checkASTStructure(child); + const auto * func = child->as(); + const auto * value_literal = func->arguments->children[1]->as(); + + if (!value_literal + || (value_literal->value.getType() != Field::Types::UInt64 && value_literal->value.getType() != Field::Types::Int64)) + throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", + ErrorCodes::UNEXPECTED_AST_STRUCTURE); + + literal_child_assign_num = value_literal->value.get(); + } assign_number_child.emplace_back(child); } + else + throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", + ErrorCodes::UNEXPECTED_AST_STRUCTURE); + + is_first_child = false; } - if (func_child_count > 0 && literal_child_count > 0) - throw Exception("ALL Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", + if (assign_count != 0 && assign_count != arguments->children.size() - 1) + throw Exception("All elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); arguments->children = assign_number_child; @@ -243,7 +263,7 @@ static DataTypePtr createExact(const ASTPtr & arguments) || !value_literal || name_literal->value.getType() != Field::Types::String || (value_literal->value.getType() != Field::Types::UInt64 && value_literal->value.getType() != Field::Types::Int64)) - throw Exception("Elements of Enum data type must be of form: 'name' = number, where name is string literal and number is an integer", + throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); const String & field_name = name_literal->value.get(); @@ -275,7 +295,7 @@ static DataTypePtr create(const ASTPtr & arguments) if (!value_literal || (value_literal->value.getType() != Field::Types::UInt64 && value_literal->value.getType() != Field::Types::Int64)) - throw Exception("Elements of Enum data type must be of form: 'name' = number, where name is string literal and number is an integer", + throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); Int64 value = value_literal->value.get(); diff --git a/tests/queries/0_stateless/00757_enum_defaults.reference b/tests/queries/0_stateless/00757_enum_defaults.reference index 35ef3c72406..6bf5cc85e87 100644 --- a/tests/queries/0_stateless/00757_enum_defaults.reference +++ b/tests/queries/0_stateless/00757_enum_defaults.reference @@ -6,3 +6,12 @@ iphone 1 \N 1 a b +1 +2 +a +a +b +-1000 +-999 +b +-1 diff --git a/tests/queries/0_stateless/00757_enum_defaults.sql b/tests/queries/0_stateless/00757_enum_defaults.sql index 769579ffc0b..71edc83abe2 100644 --- a/tests/queries/0_stateless/00757_enum_defaults.sql +++ b/tests/queries/0_stateless/00757_enum_defaults.sql @@ -1,12 +1,41 @@ select os_name, count() from (SELECT CAST('iphone' AS Enum8('iphone' = 1, 'android' = 2)) AS os_name) group by os_name WITH TOTALS; select toNullable(os_name) AS os_name, count() from (SELECT CAST('iphone' AS Enum8('iphone' = 1, 'android' = 2)) AS os_name) group by os_name WITH TOTALS; -DROP TABLE IF EXISTS auto_assgin_enum; -DROP TABLE IF EXISTS auto_assgin_enum1; +DROP TABLE IF EXISTS auto_assign_enum; +DROP TABLE IF EXISTS auto_assign_enum1; +DROP TABLE IF EXISTS auto_assign_enum2; +DROP TABLE IF EXISTS auto_assign_enum3; -CREATE TABLE auto_assgin_enum (x enum('a', 'b')) ENGINE=MergeTree() order by x; -CREATE TABLE auto_assgin_enum1 (x enum('a' = 1, 'b')) ENGINE=MergeTree() order by x; -- { serverError 223 } -INSERT INTO auto_assgin_enum VALUES('a'), ('b'); -select * from auto_assgin_enum; +CREATE TABLE auto_assign_enum (x enum('a', 'b')) ENGINE=MergeTree() order by x; +INSERT INTO auto_assign_enum VALUES('a'), ('b'); +select * from auto_assign_enum; +select CAST(x, 'Int8') from auto_assign_enum; +select * from auto_assign_enum where x = 1; -DROP TABLE auto_assgin_enum; +CREATE TABLE auto_assign_enum1 (x enum('a' = -1000, 'b')) ENGINE=MergeTree() order by x; +INSERT INTO auto_assign_enum1 VALUES('a'), ('b'); +select * from auto_assign_enum1; +select CAST(x, 'Int16') from auto_assign_enum1; +select * from auto_assign_enum1 where x = -999; + +CREATE TABLE auto_assign_enum2 (x enum('a' = -1000, 'b', 'c' = -99)) ENGINE=MergeTree() order by x; -- { serverError 223 } + +CREATE TABLE auto_assign_enum2 (x Enum8( + '00' = -128 ,'01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F', + '10','11','12','13','14','15','16','17','18','19','1A','1B','1C','1D','1E','1F', + '20','21','22','23','24','25','26','27','28','29','2A','2B','2C','2D','2E','2F', + '30','31','32','33','34','35','36','37','38','39','3A','3B','3C','3D','3E','3F', + '40','41','42','43','44','45','46','47','48','49','4A','4B','4C','4D','4E','4F', + '50','51','52','53','54','55','56','57','58','59','5A','5B','5C','5D','5E','5F', + '60','61','62','63','64','65','66','67','68','69','6A','6B','6C','6D','6E','6F', + '70','71','72','73','74','75','76','77','78','79','7A','7B','7C','7D','7E','7F' + )) ENGINE=MergeTree() order by x; + +INSERT INTO auto_assign_enum2 VALUES('7F'); +select CAST(x, 'Int8') from auto_assign_enum2; + +CREATE TABLE auto_assign_enum3 (x enum('a', 'b', NULL)) ENGINE=MergeTree() order by x; -- { serverError 223 } + +DROP TABLE auto_assign_enum; +DROP TABLE auto_assign_enum1; +DROP TABLE auto_assign_enum2;