Merge pull request #36352 from awakeljw/fork_chmaster

Possible range issues in automatic assigned enums, also fix error message.
This commit is contained in:
Yakov Olkhovskiy 2022-04-19 23:34:35 -04:00 committed by GitHub
commit f6a7b6c2a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 18 deletions

View File

@ -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

View File

@ -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

View File

@ -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<ASTLiteral>())
{
ASTPtr func = makeASTFunction("equals", child, std::make_shared<ASTLiteral>(++literal_child_count));
assign_count += !is_first_child;
ASTPtr func = makeASTFunction("equals", child, std::make_shared<ASTLiteral>(literal_child_assign_num + assign_count));
assign_number_child.emplace_back(func);
}
else
else if (child->as<ASTFunction>())
{
++func_child_count;
if (is_first_child)
{
checkASTStructure(child);
const auto * func = child->as<ASTFunction>();
const auto * value_literal = func->arguments->children[1]->as<ASTLiteral>();
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<Int64>();
}
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<String>();
@ -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<Int64>();

View File

@ -6,3 +6,12 @@ iphone 1
\N 1
a
b
1
2
a
a
b
-1000
-999
b
-1

View File

@ -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;