diff --git a/dbms/src/Parsers/ParserDictionary.cpp b/dbms/src/Parsers/ParserDictionary.cpp index 9e570d03246..09ead759d23 100644 --- a/dbms/src/Parsers/ParserDictionary.cpp +++ b/dbms/src/Parsers/ParserDictionary.cpp @@ -17,8 +17,24 @@ namespace DB bool ParserDictionaryLifetime::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) { + ParserLiteral literal_p; ParserKeyValuePairsList key_value_pairs_p; ASTPtr ast_lifetime; + auto res = std::make_shared(); + + /// simple lifetime with only maximum value e.g. LIFETIME(300) + if (literal_p.parse(pos, ast_lifetime, expected)) + { + auto literal = ast_lifetime->as(); + + if (literal.value.getType() != Field::Types::UInt64) + return false; + + res->max_sec = literal.value.get(); + node = res; + return true; + } + if (!key_value_pairs_p.parse(pos, ast_lifetime, expected)) return false; @@ -26,7 +42,7 @@ bool ParserDictionaryLifetime::parseImpl(Pos & pos, ASTPtr & node, Expected & ex if (expr_list.children.size() != 2) return false; - auto res = std::make_shared(); + bool initialized_max = false; for (const auto & elem : expr_list.children) { const ASTPair & pair = elem->as(); @@ -40,12 +56,15 @@ bool ParserDictionaryLifetime::parseImpl(Pos & pos, ASTPtr & node, Expected & ex if (pair.first == "min") res->min_sec = literal->value.get(); else if (pair.first == "max") + { res->max_sec = literal->value.get(); + initialized_max = true; + } else return false; } - if (!res->max_sec || !res->min_sec) + if (!initialized_max) return false; node = res; diff --git a/dbms/src/Parsers/ParserDictionaryAttributeDeclaration.cpp b/dbms/src/Parsers/ParserDictionaryAttributeDeclaration.cpp index 7d1d4d9b445..6188b2ecab3 100644 --- a/dbms/src/Parsers/ParserDictionaryAttributeDeclaration.cpp +++ b/dbms/src/Parsers/ParserDictionaryAttributeDeclaration.cpp @@ -34,34 +34,46 @@ bool ParserDictionaryAttributeDeclaration::parseImpl(Pos & pos, ASTPtr & node, E bool injective = false; bool is_object_id = false; - /// TODO(alesapin) Loop here to avoid strict order - if (!s_default.check_without_moving(pos, expected) && - !s_expression.check_without_moving(pos, expected) && - !s_hierarchical.check_without_moving(pos, expected) && - !s_injective.check_without_moving(pos, expected) && - !s_is_object_id.check_without_moving(pos, expected)) + if (!type_parser.parse(pos, type, expected)) + return false; + + while(true) { - if (!type_parser.parse(pos, type, expected)) - return false; + if (!default_value && s_default.ignore(pos, expected)) + { + if (!default_parser.parse(pos, default_value, expected)) + return false; + continue; + } + + if (!expression && s_expression.ignore(pos, expected)) + { + if (!expression_parser.parse(pos, expression, expected)) + return false; + continue; + } + + if (!hierarchical && s_hierarchical.ignore(pos, expected)) + { + hierarchical = true; + continue; + } + + if (!injective && s_injective.ignore(pos, expected)) + { + injective = true; + continue; + } + + if (!is_object_id && s_is_object_id.ignore(pos, expected)) + { + is_object_id = true; + continue; + } + + break; } - if (s_default.ignore(pos, expected)) - if (!default_parser.parse(pos, default_value, expected)) - return false; - - if (s_expression.ignore(pos, expected)) - if (!expression_parser.parse(pos, expression, expected)) - return false; - - if (s_hierarchical.ignore(pos, expected)) - hierarchical = true; - - if (s_injective.ignore(pos, expected)) - injective = true; - - if (s_is_object_id.ignore(pos, expected)) - is_object_id = true; - auto attribute_declaration = std::make_shared(); node = attribute_declaration; tryGetIdentifierNameInto(name, attribute_declaration->name); diff --git a/dbms/src/Parsers/tests/gtest_create_dictionary_parser.cpp b/dbms/src/Parsers/tests/gtest_create_dictionary_parser.cpp index 09eb0d1aef4..e8094f11563 100644 --- a/dbms/src/Parsers/tests/gtest_create_dictionary_parser.cpp +++ b/dbms/src/Parsers/tests/gtest_create_dictionary_parser.cpp @@ -166,10 +166,62 @@ TEST(ParserCreateDictionary, AttributesWithMultipleProperties) EXPECT_EQ(attributes_children[2]->as()->is_object_id, false); } +TEST(ParserCreateDictionary, CustomAttributePropertiesOrder) +{ + String input = " CREATE DICTIONARY dict3" + " (" + " key_column UInt64 IS_OBJECT_ID DEFAULT 100," + " second_column UInt8 INJECTIVE HIERARCHICAL DEFAULT 1," + " third_column UInt8 EXPRESSION rand() % 100 * 77 DEFAULT 2 INJECTIVE HIERARCHICAL" + " )" + " PRIMARY KEY key_column" + " SOURCE(CLICKHOUSE(REPLICA(HOST '127.0.0.1' PRIORITY 1)))" + " LIFETIME(300)"; + + ParserCreateDictionaryQuery parser; + ASTPtr ast = parseQuery(parser, input.data(), input.data() + input.size(), "", 0); + ASTCreateQuery * create = ast->as(); + + /// test attributes + EXPECT_NE(create->dictionary_attributes_list, nullptr); + + auto attributes_children = create->dictionary_attributes_list->children; + + EXPECT_EQ(attributes_children[0]->as()->name, "key_column"); + EXPECT_EQ(attributes_children[1]->as()->name, "second_column"); + EXPECT_EQ(attributes_children[2]->as()->name, "third_column"); + + EXPECT_EQ(attributes_children[0]->as()->default_value->as()->value.get(), 100); + EXPECT_EQ(attributes_children[1]->as()->default_value->as()->value.get(), 1); + EXPECT_EQ(attributes_children[2]->as()->default_value->as()->value.get(), 2); + + EXPECT_EQ(attributes_children[0]->as()->expression, nullptr); + EXPECT_EQ(attributes_children[1]->as()->expression, nullptr); + EXPECT_EQ(serializeAST(*attributes_children[2]->as()->expression, true), "(rand() % 100) * 77"); + + EXPECT_EQ(attributes_children[0]->as()->hierarchical, false); + EXPECT_EQ(attributes_children[1]->as()->hierarchical, true); + EXPECT_EQ(attributes_children[2]->as()->hierarchical, true); + + EXPECT_EQ(attributes_children[0]->as()->injective, false); + EXPECT_EQ(attributes_children[1]->as()->injective, true); + EXPECT_EQ(attributes_children[2]->as()->injective, true); + + EXPECT_EQ(attributes_children[0]->as()->is_object_id, true); + EXPECT_EQ(attributes_children[1]->as()->is_object_id, false); + EXPECT_EQ(attributes_children[2]->as()->is_object_id, false); + + /// lifetime test + auto lifetime = create->dictionary->lifetime; + + EXPECT_EQ(lifetime->min_sec, 0); + EXPECT_EQ(lifetime->max_sec, 300); +} + TEST(ParserCreateDictionary, NestedSource) { - String input = " CREATE DICTIONARY dict3" + String input = " CREATE DICTIONARY dict4" " (" " key_column UInt64," " second_column UInt8," @@ -184,7 +236,7 @@ TEST(ParserCreateDictionary, NestedSource) ParserCreateDictionaryQuery parser; ASTPtr ast = parseQuery(parser, input.data(), input.data() + input.size(), "", 0); ASTCreateQuery * create = ast->as(); - EXPECT_EQ(create->table, "dict3"); + EXPECT_EQ(create->table, "dict4"); EXPECT_EQ(create->database, ""); /// source test @@ -212,3 +264,27 @@ TEST(ParserCreateDictionary, NestedSource) EXPECT_EQ(children[4]->as()->first, "password"); EXPECT_EQ(children[4]->as()->second->as()->value.get(), ""); } + + + +TEST(ParserCreateDictionary, Formatting) +{ + String input = " CREATE DICTIONARY test.dict5" + " (" + " key_column1 UInt64 DEFAULT 1 HIERARCHICAL INJECTIVE," + " key_column2 String DEFAULT ''," + " second_column UInt8 EXPRESSION intDiv(50, rand() % 1000)," + " third_column UInt8" + " )" + " PRIMARY KEY key_column1, key_column2" + " SOURCE(MYSQL(HOST 'localhost' PORT 9000 USER 'default' REPLICA(HOST '127.0.0.1' PRIORITY 1) PASSWORD ''))" + " LAYOUT(CACHE(size_in_cells 50))" + " LIFETIME(MIN 1 MAX 10)" + " RANGE(MIN second_column MAX third_column)"; + + ParserCreateDictionaryQuery parser; + ASTPtr ast = parseQuery(parser, input.data(), input.data() + input.size(), "", 0); + ASTCreateQuery * create = ast->as(); + auto str = serializeAST(*create, true); + EXPECT_EQ(str, "CREATE DICTIONARY test.dict5 (`key_column1` UInt64 DEFAULT 1 HIERARCHICAL INJECTIVE, `key_column2` String DEFAULT '', `second_column` UInt8 EXPRESSION intDiv(50, rand() % 1000), `third_column` UInt8) PRIMARY KEY key_column1, key_column2 MYSQL(HOST 'localhost' PORT 9000 USER 'default' REPLICA (HOST '127.0.0.1' PRIORITY 1) PASSWORD '') LIFETIME(MIN 1, MAX 10) LAYOUT(CACHE(SIZE_IN_CELLS 50)) RANGE(MIN second_column, MAX third_column)"); +}