diff options
| author | Innokentii Mokin <[email protected]> | 2024-08-05 16:36:16 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-08-05 16:36:16 +0300 |
| commit | 63c9ea744bf7a7ecfad8a060f0caa2caa97fdc73 (patch) | |
| tree | bb0a8efb0fe8afb5d3255ff24c47cf5c89436db6 | |
| parent | 7d65202191dcda49663ac6ba058751c79d6627a3 (diff) | |
Add backup collection sql (#7255)
| -rw-r--r-- | ydb/core/testlib/basics/feature_flags.h | 1 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/SQLv1.g.in | 22 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/format/sql_format.cpp | 35 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/format/sql_format_ut.cpp | 14 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/sql.cpp | 3 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/sql_query.cpp | 84 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/sql_translation.cpp | 85 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/sql_translation.h | 4 | ||||
| -rw-r--r-- | ydb/library/yql/sql/v1/sql_ut.cpp | 95 |
9 files changed, 340 insertions, 3 deletions
diff --git a/ydb/core/testlib/basics/feature_flags.h b/ydb/core/testlib/basics/feature_flags.h index 25426b836d7..8553a4f861d 100644 --- a/ydb/core/testlib/basics/feature_flags.h +++ b/ydb/core/testlib/basics/feature_flags.h @@ -62,6 +62,7 @@ public: FEATURE_FLAG_SETTER(EnableTableDatetime64) FEATURE_FLAG_SETTER(EnableResourcePools) FEATURE_FLAG_SETTER(EnableChangefeedsOnIndexTables) + FEATURE_FLAG_SETTER(EnableBackupService) #undef FEATURE_FLAG_SETTER }; diff --git a/ydb/library/yql/sql/v1/SQLv1.g.in b/ydb/library/yql/sql/v1/SQLv1.g.in index 5f8f7b44c67..ec4aea282bd 100644 --- a/ydb/library/yql/sql/v1/SQLv1.g.in +++ b/ydb/library/yql/sql/v1/SQLv1.g.in @@ -66,6 +66,9 @@ sql_stmt_core: | create_resource_pool_stmt | alter_resource_pool_stmt | drop_resource_pool_stmt + | create_backup_collection_stmt + | alter_backup_collection_stmt + | drop_backup_collection_stmt ; expr: @@ -644,6 +647,19 @@ create_table_entry: | an_id_schema ; +create_backup_collection_stmt: CREATE backup_collection WITH LPAREN backup_collection_settings RPAREN; +alter_backup_collection_stmt: ALTER backup_collection alter_backup_collection_actions; +drop_backup_collection_stmt: DROP backup_collection; + +alter_backup_collection_actions: alter_backup_collection_action (COMMA alter_backup_collection_action)*; +alter_backup_collection_action: + alter_table_set_table_setting_compat + | alter_table_reset_table_setting +; +backup_collection: BACKUP COLLECTION object_ref; +backup_collection_settings: backup_collection_settings_entry (COMMA backup_collection_settings_entry)*; +backup_collection_settings_entry: an_id EQUALS table_setting_value; + table_inherits: INHERITS LPAREN simple_table_ref_core (COMMA simple_table_ref_core)* RPAREN; table_partition_by: PARTITION BY HASH pure_column_list; with_table_settings: WITH LPAREN table_settings_entry (COMMA table_settings_entry)* RPAREN; @@ -1204,6 +1220,7 @@ keyword_as_compat: | ATTACH | ATTRIBUTES | AUTOINCREMENT + | BACKUP | BEFORE | BEGIN | BERNOULLI @@ -1212,6 +1229,7 @@ keyword_as_compat: | CHANGEFEED | CHECK // | COLLATE + | COLLECTION | COMMIT | CONDITIONAL | CONFLICT @@ -1416,6 +1434,7 @@ keyword_compat: ( | ATTACH | ATTRIBUTES | AUTOINCREMENT + | BACKUP | BEFORE | BEGIN | BERNOULLI @@ -1424,6 +1443,7 @@ keyword_compat: ( | CHANGEFEED | CHECK | COLLATE + | COLLECTION | COMMIT | CONDITIONAL | CONFLICT @@ -1727,6 +1747,8 @@ ATTACH: A T T A C H; ATTRIBUTES: A T T R I B U T E S; AUTOINCREMENT: A U T O I N C R E M E N T; AUTOMAP: A U T O M A P; +BACKUP: B A C K U P; +COLLECTION: C O L L E C T I O N; BEFORE: B E F O R E; BEGIN: B E G I N; BERNOULLI: B E R N O U L L I; diff --git a/ydb/library/yql/sql/v1/format/sql_format.cpp b/ydb/library/yql/sql/v1/format/sql_format.cpp index 0f1744306cc..746d1a0d7cf 100644 --- a/ydb/library/yql/sql/v1/format/sql_format.cpp +++ b/ydb/library/yql/sql/v1/format/sql_format.cpp @@ -1527,6 +1527,36 @@ private: VisitAllFields(TRule_drop_resource_pool_stmt::GetDescriptor(), msg); } + void VisitCreateBackupCollection(const TRule_create_backup_collection_stmt& msg) { + PosFromToken(msg.GetToken1()); + NewLine(); + VisitAllFields(TRule_create_backup_collection_stmt::GetDescriptor(), msg); + } + + void VisitAlterBackupCollection(const TRule_alter_backup_collection_stmt& msg) { + PosFromToken(msg.GetToken1()); + NewLine(); + VisitToken(msg.GetToken1()); + Visit(msg.GetRule_backup_collection2()); + + NewLine(); + PushCurrentIndent(); + Visit(msg.GetRule_alter_backup_collection_actions3().GetRule_alter_backup_collection_action1()); + for (const auto& action : msg.GetRule_alter_backup_collection_actions3().GetBlock2()) { + Visit(action.GetToken1()); // comma + NewLine(); + Visit(action.GetRule_alter_backup_collection_action2()); + } + + PopCurrentIndent(); + } + + void VisitDropBackupCollection(const TRule_drop_backup_collection_stmt& msg) { + PosFromToken(msg.GetToken1()); + NewLine(); + VisitAllFields(TRule_drop_backup_collection_stmt::GetDescriptor(), msg); + } + void VisitAllFields(const NProtoBuf::Descriptor* descr, const NProtoBuf::Message& msg) { VisitAllFieldsImpl<TPrettyVisitor, &TPrettyVisitor::Visit>(this, descr, msg); } @@ -2743,7 +2773,10 @@ TStaticData::TStaticData() {TRule_drop_view_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropView)}, {TRule_create_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCreateResourcePool)}, {TRule_alter_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitAlterResourcePool)}, - {TRule_drop_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropResourcePool)} + {TRule_drop_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropResourcePool)}, + {TRule_create_backup_collection_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCreateBackupCollection)}, + {TRule_alter_backup_collection_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitAlterBackupCollection)}, + {TRule_drop_backup_collection_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropBackupCollection)}, }) , ObfuscatingVisitDispatch({ {TToken::GetDescriptor(), MakeObfuscatingFunctor(&TObfuscatingVisitor::VisitToken)}, diff --git a/ydb/library/yql/sql/v1/format/sql_format_ut.cpp b/ydb/library/yql/sql/v1/format/sql_format_ut.cpp index 3e386d5999b..cc394463cd4 100644 --- a/ydb/library/yql/sql/v1/format/sql_format_ut.cpp +++ b/ydb/library/yql/sql/v1/format/sql_format_ut.cpp @@ -1605,4 +1605,18 @@ FROM Input MATCH_RECOGNIZE (PATTERN (A) DEFINE A AS A); TSetup setup; setup.Run(cases); } + + Y_UNIT_TEST(BackupCollectionOperations) { + TCases cases = { + {"creAte BackuP colLection `-naMe` wIth (a = \"b\")", + "CREATE BACKUP COLLECTION `-naMe` WITH (a = \"b\");\n"}, + {"alTer bACKuP coLLECTION naMe resEt (b, c), seT (x=y, z=false)", + "ALTER BACKUP COLLECTION naMe\n\tRESET (b, c),\n\tSET (x = y, z = FALSE);\n"}, + {"DROP backup collectiOn `/some/path`", + "DROP BACKUP COLLECTION `/some/path`;\n"}, + }; + + TSetup setup; + setup.Run(cases); + } } diff --git a/ydb/library/yql/sql/v1/sql.cpp b/ydb/library/yql/sql/v1/sql.cpp index e50c776269e..5f539744f8f 100644 --- a/ydb/library/yql/sql/v1/sql.cpp +++ b/ydb/library/yql/sql/v1/sql.cpp @@ -167,6 +167,9 @@ bool NeedUseForAllStatements(const TRule_sql_stmt_core::AltCase& subquery) { case TRule_sql_stmt_core::kAltSqlStmtCore45: // create resource pool case TRule_sql_stmt_core::kAltSqlStmtCore46: // alter resource pool case TRule_sql_stmt_core::kAltSqlStmtCore47: // drop resource pool + case TRule_sql_stmt_core::kAltSqlStmtCore48: // create backup collection + case TRule_sql_stmt_core::kAltSqlStmtCore49: // alter backup collection + case TRule_sql_stmt_core::kAltSqlStmtCore50: // drop backup collection return false; } } diff --git a/ydb/library/yql/sql/v1/sql_query.cpp b/ydb/library/yql/sql/v1/sql_query.cpp index 479330725dd..d374e89f803 100644 --- a/ydb/library/yql/sql/v1/sql_query.cpp +++ b/ydb/library/yql/sql/v1/sql_query.cpp @@ -1344,6 +1344,90 @@ bool TSqlQuery::Statement(TVector<TNodePtr>& blocks, const TRule_sql_stmt_core& AddStatementToBlocks(blocks, BuildDropObjectOperation(Ctx.Pos(), objectId, "RESOURCE_POOL", false, {}, context)); break; } + case TRule_sql_stmt_core::kAltSqlStmtCore48: { + // create_backup_collection_stmt: CREATE BACKUP COLLECTION name WITH (k=v,...); + auto& node = core.GetAlt_sql_stmt_core48().GetRule_create_backup_collection_stmt1(); + TObjectOperatorContext context(Ctx.Scoped); + if (node.GetRule_backup_collection2().GetRule_object_ref3().HasBlock1()) { + if (!ClusterExpr(node.GetRule_backup_collection2().GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(), + false, + context.ServiceId, + context.Cluster)) { + return false; + } + } + + std::map<TString, TDeferredAtom> kv; + if (!ParseBackupCollectionSettings(kv, node.GetRule_backup_collection_settings5())) { + return false; + } + + const TString& objectId = Id(node.GetRule_backup_collection2().GetRule_object_ref3().GetRule_id_or_at2(), *this).second; + constexpr const char* typeId = "BACKUP_COLLECTION"; + AddStatementToBlocks(blocks, + BuildCreateObjectOperation(Ctx.Pos(), + BuildTablePath(Ctx.GetPrefixPath(context.ServiceId, context.Cluster), objectId), + typeId, + false, + false, + std::move(kv), + context)); + break; + } + case TRule_sql_stmt_core::kAltSqlStmtCore49: { + // alter_backup_collection_stmt: ALTER BACKUP COLLECTION name alter_backup_collection_action (COMMA alter_backup_collection_action)*; + auto& node = core.GetAlt_sql_stmt_core49().GetRule_alter_backup_collection_stmt1(); + TObjectOperatorContext context(Ctx.Scoped); + if (node.GetRule_backup_collection2().GetRule_object_ref3().HasBlock1()) { + if (!ClusterExpr(node.GetRule_backup_collection2().GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(), + false, + context.ServiceId, + context.Cluster)) { + return false; + } + } + + std::map<TString, TDeferredAtom> kv; + std::set<TString> toReset; + if (!ParseBackupCollectionSettings(kv, toReset, node.GetRule_alter_backup_collection_actions3())) { + return false; + } + + const TString& objectId = Id(node.GetRule_backup_collection2().GetRule_object_ref3().GetRule_id_or_at2(), *this).second; + constexpr const char* typeId = "BACKUP_COLLECTION"; + AddStatementToBlocks(blocks, + BuildAlterObjectOperation(Ctx.Pos(), + BuildTablePath(Ctx.GetPrefixPath(context.ServiceId, context.Cluster), objectId), + typeId, + std::move(kv), + std::move(toReset), + context)); + break; + } + case TRule_sql_stmt_core::kAltSqlStmtCore50: { + // drop_backup_collection_stmt: DROP BACKUP COLLECTION name; + auto& node = core.GetAlt_sql_stmt_core50().GetRule_drop_backup_collection_stmt1(); + TObjectOperatorContext context(Ctx.Scoped); + if (node.GetRule_backup_collection2().GetRule_object_ref3().HasBlock1()) { + if (!ClusterExpr(node.GetRule_backup_collection2().GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(), + false, + context.ServiceId, + context.Cluster)) { + return false; + } + } + + const TString& objectId = Id(node.GetRule_backup_collection2().GetRule_object_ref3().GetRule_id_or_at2(), *this).second; + constexpr const char* typeId = "BACKUP_COLLECTION"; + AddStatementToBlocks(blocks, + BuildDropObjectOperation(Ctx.Pos(), + BuildTablePath(Ctx.GetPrefixPath(context.ServiceId, context.Cluster), objectId), + typeId, + false, + {}, + context)); + break; + } case TRule_sql_stmt_core::ALT_NOT_SET: Ctx.IncrementMonCounter("sql_errors", "UnknownStatement" + internalStatementName); AltNotImplemented("sql_stmt_core", core); diff --git a/ydb/library/yql/sql/v1/sql_translation.cpp b/ydb/library/yql/sql/v1/sql_translation.cpp index fcd0f1ebb10..bf473573f4c 100644 --- a/ydb/library/yql/sql/v1/sql_translation.cpp +++ b/ydb/library/yql/sql/v1/sql_translation.cpp @@ -3872,6 +3872,91 @@ bool TSqlTranslation::PermissionNameClause(const TRule_permission_name_target& n return true; } +bool TSqlTranslation::StoreStringSettingsEntry(const TIdentifier& id, const TRule_table_setting_value* value, std::map<TString, TDeferredAtom>& result) { + YQL_ENSURE(value); + + const TString key = to_lower(id.Name); + if (result.find(key) != result.end()) { + Ctx.Error() << to_upper(key) << " duplicate keys"; + return false; + } + + switch (value->Alt_case()) { + case TRule_table_setting_value::kAltTableSettingValue2: + return StoreString(*value, result[key], Ctx, to_upper(key)); + + default: + Ctx.Error() << to_upper(key) << " value should be a string literal"; + return false; + } + + return true; +} + +bool TSqlTranslation::StoreStringSettingsEntry(const TRule_alter_table_setting_entry& entry, std::map<TString, TDeferredAtom>& result) { + const TIdentifier id = IdEx(entry.GetRule_an_id1(), *this); + return StoreStringSettingsEntry(id, &entry.GetRule_table_setting_value3(), result); +} + +bool TSqlTranslation::ParseBackupCollectionSettings(std::map<TString, TDeferredAtom>& result, const TRule_backup_collection_settings& settings) { + const auto& firstEntry = settings.GetRule_backup_collection_settings_entry1(); + if (!StoreStringSettingsEntry(IdEx(firstEntry.GetRule_an_id1(), *this), &firstEntry.GetRule_table_setting_value3(), result)) { + return false; + } + for (const auto& block : settings.GetBlock2()) { + const auto& entry = block.GetRule_backup_collection_settings_entry2(); + if (!StoreStringSettingsEntry(IdEx(entry.GetRule_an_id1(), *this), &entry.GetRule_table_setting_value3(), result)) { + return false; + } + } + return true; +} + + +bool TSqlTranslation::ParseBackupCollectionSettings(std::map<TString, TDeferredAtom>& result, std::set<TString>& toReset, const TRule_alter_backup_collection_actions& actions) { + auto parseAction = [&](auto& actionVariant) { + switch (actionVariant.Alt_case()) { + case TRule_alter_backup_collection_action::kAltAlterBackupCollectionAction1: { + const auto& action = actionVariant.GetAlt_alter_backup_collection_action1().GetRule_alter_table_set_table_setting_compat1(); + if (!StoreStringSettingsEntry(action.GetRule_alter_table_setting_entry3(), result)) { + return false; + } + for (const auto& entry : action.GetBlock4()) { + if (!StoreStringSettingsEntry(entry.GetRule_alter_table_setting_entry2(), result)) { + return false; + } + } + return true; + } + case TRule_alter_backup_collection_action::kAltAlterBackupCollectionAction2: { + const auto& action = actionVariant.GetAlt_alter_backup_collection_action2().GetRule_alter_table_reset_table_setting1(); + const TString firstKey = to_lower(IdEx(action.GetRule_an_id3(), *this).Name); + toReset.insert(firstKey); + for (const auto& key : action.GetBlock4()) { + toReset.insert(to_lower(IdEx(key.GetRule_an_id2(), *this).Name)); + } + return true; + } + case TRule_alter_backup_collection_action::ALT_NOT_SET: + Y_ABORT("You should change implementation according to grammar changes"); + } + }; + + const auto& firstAction = actions.GetRule_alter_backup_collection_action1(); + if (!parseAction(firstAction)) { + return false; + } + + for (const auto& action : actions.GetBlock2()) { + if (!parseAction(action.GetRule_alter_backup_collection_action2())) { + return false; + } + } + + + return true; +} + TString TSqlTranslation::FrameSettingsToString(EFrameSettings settings, bool isUnbounded) { TString result; switch (settings) { diff --git a/ydb/library/yql/sql/v1/sql_translation.h b/ydb/library/yql/sql/v1/sql_translation.h index 2646a20c987..997f1079d71 100644 --- a/ydb/library/yql/sql/v1/sql_translation.h +++ b/ydb/library/yql/sql/v1/sql_translation.h @@ -242,6 +242,10 @@ protected: bool PermissionNameClause(const TRule_permission_name_target& node, TVector<TDeferredAtom>& result, bool withGrantOption); bool PermissionNameClause(const TRule_permission_name& node, TDeferredAtom& result); bool PermissionNameClause(const TRule_permission_id& node, TDeferredAtom& result); + bool StoreStringSettingsEntry(const TIdentifier& id, const TRule_table_setting_value* value, std::map<TString, TDeferredAtom>& result); + bool StoreStringSettingsEntry(const TRule_alter_table_setting_entry& entry, std::map<TString, TDeferredAtom>& result); + bool ParseBackupCollectionSettings(std::map<TString, TDeferredAtom>& result, const TRule_backup_collection_settings& settings); + bool ParseBackupCollectionSettings(std::map<TString, TDeferredAtom>& result, std::set<TString>& toReset, const TRule_alter_backup_collection_actions& actions); bool ValidateAuthMethod(const std::map<TString, TDeferredAtom>& result); bool ValidateExternalTable(const TCreateTableParameters& params); diff --git a/ydb/library/yql/sql/v1/sql_ut.cpp b/ydb/library/yql/sql/v1/sql_ut.cpp index 01a6f86e84d..ec86d34a8d5 100644 --- a/ydb/library/yql/sql/v1/sql_ut.cpp +++ b/ydb/library/yql/sql/v1/sql_ut.cpp @@ -2521,12 +2521,12 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { Y_UNIT_TEST(AlterTableAddIndexWithIsNotSupported) { ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx GLOBAL ON (col) WITH (a=b)", - "<main>:1:40: Error: with: alternative is not implemented yet: 726:20: global_index\n"); + "<main>:1:40: Error: with: alternative is not implemented yet: 742:20: global_index\n"); } Y_UNIT_TEST(AlterTableAddIndexLocalIsNotSupported) { ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx LOCAL ON (col)", - "<main>:1:40: Error: local: alternative is not implemented yet: 726:35: local_index\n"); + "<main>:1:40: Error: local: alternative is not implemented yet: 742:35: local_index\n"); } Y_UNIT_TEST(CreateTableAddIndexVector) { @@ -7030,3 +7030,94 @@ Y_UNIT_TEST_SUITE(ResourcePool) { UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); } } + +Y_UNIT_TEST_SUITE(BackupCollection) { + Y_UNIT_TEST(CreateBackupCollection) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection WITH ( + STORAGE="local", + TAG="test" -- for testing purposes, not a real thing + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"storage" '"local") '('"tag" '"test"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateBackupCollectionWithBadArguments) { + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection; + )sql" , "<main>:3:55: Error: Unexpected token ';' : syntax error...\n\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection WITH ( + DUPLICATE_SETTING="first_value", + DUPLICATE_SETTING="second_value" + ); + )sql" , "<main>:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE BACKUP COLLECTION TestCollection WITH ( + INT_SETTING=1 + ); + )sql" , "<main>:4:21: Error: INT_SETTING value should be a string literal\n"); + } + + Y_UNIT_TEST(AlterBackupCollection) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER BACKUP COLLECTION TestCollection + SET (STORAGE="remote"), -- also just for test + SET (TAG1 = "123"), + RESET (TAG2, TAG3); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"storage" '"remote") '('"tag1" '"123"))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"tag2" '"tag3")))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropBackupCollection) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP BACKUP COLLECTION TestCollection; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} |
