diff options
| author | Slusarenko Igor <[email protected]> | 2026-07-15 14:08:44 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-15 14:08:44 +0300 |
| commit | f3db6838ad5e3bb3289849c554b35bc665f23bd7 (patch) | |
| tree | 9710e07453ed14c7b7b520c984a327addc8181fb | |
| parent | 9b22d24959121e1a56f8ca67c23b902d82a50842 (diff) | |
wm: make classifier predicates match the RFC (#46499)
13 files changed, 148 insertions, 142 deletions
diff --git a/ydb/core/kqp/workload_service/kqp_query_classifier.cpp b/ydb/core/kqp/workload_service/kqp_query_classifier.cpp index 0ba7bf442bd..244cdab017c 100644 --- a/ydb/core/kqp/workload_service/kqp_query_classifier.cpp +++ b/ydb/core/kqp/workload_service/kqp_query_classifier.cpp @@ -154,10 +154,10 @@ private: } /// - /// Check Predicate HasAppName + /// Check Predicate HasAppName — strict string equality. /// - static bool MatchesAppName(const std::optional<NResourcePool::TRegexPredicate>& predicate, const TString& appName) { - return !predicate || predicate->Match(appName); + static bool MatchesAppName(const std::optional<TString>& expected, const TString& appName) { + return !expected || *expected == appName; } /// diff --git a/ydb/core/kqp/workload_service/ut/kqp_has_app_name_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_has_app_name_ut.cpp index dd729034a13..a4ef96f6ac0 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_has_app_name_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_has_app_name_ut.cpp @@ -32,21 +32,7 @@ Y_UNIT_TEST_SUITE(TQueryClassifierHasAppName) { UNIT_ASSERT_VALUES_EQUAL(GetPoolId(tc.RunPreClassify()), "pool_target"); } - Y_UNIT_TEST(ShouldMatchAppNameRegexSuffix) { - TClassifyTestCase tc; - tc.ClassifierHasAppName = "ydb-.*"; - tc.ContextAppName = "ydb-ui"; - UNIT_ASSERT_VALUES_EQUAL(GetPoolId(tc.RunPreClassify()), "pool_target"); - } - - Y_UNIT_TEST(ShouldMatchAppNameAlternation) { - TClassifyTestCase tc; - tc.ClassifierHasAppName = "ydb-ui|ydb-cli"; - tc.ContextAppName = "ydb-cli"; - UNIT_ASSERT_VALUES_EQUAL(GetPoolId(tc.RunPreClassify()), "pool_target"); - } - - Y_UNIT_TEST(ShouldRejectLiteralBeyondAnchor) { + Y_UNIT_TEST(ShouldRequireExactMatch) { TClassifyTestCase tc; tc.ClassifierHasAppName = "ydb-ui"; tc.ContextAppName = "ydb-ui-prod"; @@ -79,41 +65,6 @@ Y_UNIT_TEST_SUITE(HasAppNameDdl) { WaitForClassifierSuccess(ydb, noMatchSettings); } - Y_UNIT_TEST(TestHasAppNameRegexClassifier) { - auto ydb = TYdbSetupSettings().Create(); - - const TString& poolId = "regex_pool"; - const TString& userSID = "test@user"; - ydb->ExecuteSchemeQuery(TStringBuilder() << R"( - GRANT ALL ON `/)" << ydb->GetSettings().DomainName_ << R"(` TO `)" << userSID << R"(`; - CREATE RESOURCE POOL )" << poolId << R"( WITH ( - CONCURRENT_QUERY_LIMIT=0 - ); - CREATE RESOURCE POOL CLASSIFIER regex_classifier WITH ( - RESOURCE_POOL=")" << poolId << R"(", - HAS_APP_NAME="my_app.*" - ); - )"); - - auto matchSettings = TQueryRunnerSettings().PoolId("").UserSID(userSID).ApplicationName("my_app_v2"); - WaitForClassifierFail(ydb, matchSettings, poolId); - - auto noMatchSettings = TQueryRunnerSettings().PoolId("").UserSID(userSID).ApplicationName("other_app"); - WaitForClassifierSuccess(ydb, noMatchSettings); - } - - Y_UNIT_TEST(TestHasAppNameInvalidRegex) { - auto ydb = TYdbSetupSettings().Create(); - - auto result = ydb->ExecuteQuery(TStringBuilder() << R"( - CREATE RESOURCE POOL CLASSIFIER bad_regex_classifier WITH ( - RESOURCE_POOL=")" << NResourcePool::DEFAULT_POOL_ID << R"(", - HAS_APP_NAME="[invalid" - ); - )", TQueryRunnerSettings().PoolId(NResourcePool::DEFAULT_POOL_ID)); - UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), EStatus::SUCCESS); - } - Y_UNIT_TEST(TestAlterHasAppName) { auto ydb = TYdbSetupSettings().Create(); diff --git a/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_matcher_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_matcher_ut.cpp index 9b872f8ee6d..28c135ff3e3 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_matcher_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_matcher_ut.cpp @@ -36,7 +36,7 @@ TKqpReadRangesSource* AddSource(TKqpPhyQuery& phy, const TString& path) { const auto Matches = [](auto builder, auto configure) { TKqpPhyQuery phy; configure(builder(phy, TString("/Root/t"))); - return NWorkload::MatchesFullScan(TRegexPredicate::Compile("/Root/t"), phy); + return NWorkload::MatchesFullScan(TRegexPredicate::FromGlob("/Root/t"), phy); }; const auto AssertHasFullScan = [](auto c) { UNIT_ASSERT( Matches(AddOp, c)); }; @@ -55,21 +55,21 @@ Y_UNIT_TEST_SUITE(TFullScanMatcherPredicate) { } Y_UNIT_TEST(NonMatchingPathIsIgnored) { - auto pred = TRegexPredicate::Compile("/Root/critical"); + auto pred = TRegexPredicate::FromGlob("/Root/critical"); TKqpPhyQuery phy; AddOp(phy, "/Root/other")->MutableReadRange()->MutableKeyRange(); UNIT_ASSERT(!NWorkload::MatchesFullScan(pred, phy)); } - Y_UNIT_TEST(RegexPatternMatches) { - auto pred = TRegexPredicate::Compile("/Root/db/orders_archive.*"); + Y_UNIT_TEST(GlobPatternMatches) { + auto pred = TRegexPredicate::FromGlob("/Root/db/orders_archive*"); TKqpPhyQuery phy; AddOp(phy, "/Root/db/orders_archive_2024")->MutableReadRange()->MutableKeyRange(); UNIT_ASSERT(NWorkload::MatchesFullScan(pred, phy)); } Y_UNIT_TEST(MixedOpsReturnTrueOnAnyMatch) { - auto pred = TRegexPredicate::Compile("/Root/t"); + auto pred = TRegexPredicate::FromGlob("/Root/t"); TKqpPhyQuery phy; AddOp(phy, "/Root/other")->MutableReadRange()->MutableKeyRange(); AddOp(phy, "/Root/t")->MutableReadRange()->MutableKeyRange(); diff --git a/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_ut.cpp index 667755fdb83..ee69f7f5fa4 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_has_full_scan_ut.cpp @@ -53,9 +53,9 @@ Y_UNIT_TEST_SUITE(TQueryClassifierHasFullScan) { UNIT_ASSERT_VALUES_EQUAL(GetPostPoolId(result), "default"); } - Y_UNIT_TEST(ShouldMatchRegexPattern) { + Y_UNIT_TEST(ShouldMatchGlobPattern) { TClassifyTestCase tc; - tc.ClassifierHasFullScan = "/Root/testdb/orders_.*"; + tc.ClassifierHasFullScan = "/Root/testdb/orders_*"; auto result = tc.RunPostClassify("/Root/testdb/orders_2024", /*isFullScan=*/true); UNIT_ASSERT_VALUES_EQUAL(GetPostPoolId(result), "pool_target"); } diff --git a/ydb/core/kqp/workload_service/ut/kqp_has_path_ddl_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_has_path_ddl_ut.cpp index 382b19bbaf8..114721f8651 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_has_path_ddl_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_has_path_ddl_ut.cpp @@ -241,7 +241,7 @@ Y_UNIT_TEST_SUITE(HasPathSysView) { SetupRowStoreTable(ydb, userSID); ydb->ExecuteSchemeQuery(RejectClassifierDdl( - poolId, "sysview_classifier", "/Root/.sys/.*")); + poolId, "sysview_classifier", "/Root/.sys/*")); const TString query = "SELECT * FROM t;"; auto settings = NWorkload::TQueryRunnerSettings().PoolId("").UserSID(userSID); diff --git a/ydb/core/kqp/workload_service/ut/kqp_has_path_matcher_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_has_path_matcher_ut.cpp index 689f96443fe..55cd3c86ef8 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_has_path_matcher_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_has_path_matcher_ut.cpp @@ -44,32 +44,32 @@ void AddPqTopicSink(TKqpPhyQuery& phy, const TString& topicPath) { // build-and-check one-liners (mirrors kqp_has_full_scan_matcher_ut.cpp style). // Runs the matcher against a phyQuery holding one tx-level table entry. -// Predicate + path both vary so predicate-behaviour tests can exercise regex +// Predicate + path both vary so predicate-behaviour tests can exercise glob // semantics and canonicalization in isolation. const auto MatchesTxTable = [](const TString& pattern, const TString& path) { TKqpPhyQuery phy; AddTxTable(phy, path); - return NWorkload::MatchesPath(TRegexPredicate::Compile(pattern), {}, phy); + return NWorkload::MatchesPath(TRegexPredicate::FromGlob(pattern), {}, phy); }; -// Runs the matcher with pattern `/Root/t.*` against the given queryTables list. +// Runs the matcher with pattern `/Root/t*` against the given queryTables list. const auto MatchesQueryTables = [](const TVector<TString>& queryTables) { TKqpPhyQuery phy; - return NWorkload::MatchesPath(TRegexPredicate::Compile("/Root/t.*"), queryTables, phy); + return NWorkload::MatchesPath(TRegexPredicate::FromGlob("/Root/t*"), queryTables, phy); }; // Runs the matcher with pattern `/Root/db/target` against a phyQuery built by `build`. const auto MatchesTx = [](auto build) { TKqpPhyQuery phy; build(phy); - return NWorkload::MatchesPath(TRegexPredicate::Compile("/Root/db/target"), {}, phy); + return NWorkload::MatchesPath(TRegexPredicate::FromGlob("/Root/db/target"), {}, phy); }; // Runs the matcher with pattern `/Root/db/topic` against a phyQuery built by `build`. const auto MatchesTopics = [](auto build) { TKqpPhyQuery phy; build(phy); - return NWorkload::MatchesPath(TRegexPredicate::Compile("/Root/db/topic"), {}, phy); + return NWorkload::MatchesPath(TRegexPredicate::FromGlob("/Root/db/topic"), {}, phy); }; } // anonymous namespace @@ -87,8 +87,8 @@ Y_UNIT_TEST_SUITE(TPathMatcherPredicate) { UNIT_ASSERT(!MatchesTxTable("/Root/critical", "/Root/other")); } - Y_UNIT_TEST(RegexPatternMatches) { - UNIT_ASSERT(MatchesTxTable("/Root/db/archive.*", "/Root/db/archive_2024")); + Y_UNIT_TEST(GlobPatternMatches) { + UNIT_ASSERT(MatchesTxTable("/Root/db/archive*", "/Root/db/archive_2024")); } Y_UNIT_TEST(CanonizesPathWithoutLeadingSlash) { diff --git a/ydb/core/kqp/workload_service/ut/kqp_has_path_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_has_path_ut.cpp index d6d140f507e..44f22c21661 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_has_path_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_has_path_ut.cpp @@ -45,9 +45,9 @@ Y_UNIT_TEST_SUITE(TQueryClassifierHasPath) { UNIT_ASSERT_VALUES_EQUAL(GetPostPoolId(result), "default"); } - Y_UNIT_TEST(ShouldMatchRegexPattern) { + Y_UNIT_TEST(ShouldMatchGlobPattern) { TClassifyTestCase tc; - tc.ClassifierHasPath = "/Root/testdb/archive/.*"; + tc.ClassifierHasPath = "/Root/testdb/archive/*"; auto result = tc.RunPostClassifyForPath("/Root/testdb/archive/orders_2024"); UNIT_ASSERT_VALUES_EQUAL(GetPostPoolId(result), "pool_target"); } diff --git a/ydb/core/resource_pools/regex_predicate.cpp b/ydb/core/resource_pools/regex_predicate.cpp index 189f195d29e..9f1f84c7b31 100644 --- a/ydb/core/resource_pools/regex_predicate.cpp +++ b/ydb/core/resource_pools/regex_predicate.cpp @@ -8,18 +8,46 @@ namespace NKikimr::NResourcePool { -TRegexPredicate TRegexPredicate::Compile(TStringBuf pattern) { - if (pattern.empty()) { - throw yexception() << "TRegexPredicate::Compile called with empty pattern; " - << "caller must treat empty as nullopt (no filter)"; - } - TString anchored = TStringBuilder() << "^(?:" << pattern << ")$"; +namespace { + +TRegexPredicate CompileAnchored(TStringBuf originalPattern, TStringBuf translated) { + TString anchored = TStringBuilder() << "^(?:" << translated << ")$"; try { auto compiled = std::make_shared<TRegExMatch>(anchored, REG_EXTENDED | REG_NOSUB); - return TRegexPredicate{TString(pattern), std::move(compiled)}; + return TRegexPredicate{TString(originalPattern), std::move(compiled)}; } catch (...) { - throw yexception() << "Invalid regex pattern '" << pattern << "': " << CurrentExceptionMessage(); + throw yexception() << "Failed to compile pattern '" << originalPattern << "': " << CurrentExceptionMessage(); + } +} + +} // namespace + +TRegexPredicate TRegexPredicate::FromGlob(TStringBuf glob) { + if (glob.empty()) { + throw yexception() << "TRegexPredicate::FromGlob called with empty value; " + << "caller must treat empty as nullopt (no filter)"; + } + TString translated; + translated.reserve(glob.size() * 2); + for (char c : glob) { + switch (c) { + case '*': + translated += ".*"; + break; + case '?': + translated += '.'; + break; + case '.': case '\\': case '+': case '(': case ')': + case '[': case ']': case '{': case '}': + case '|': case '^': case '$': + translated += '\\'; + translated += c; + break; + default: + translated += c; + } } + return CompileAnchored(glob, translated); } bool TRegexPredicate::Match(const TString& value) const { diff --git a/ydb/core/resource_pools/regex_predicate.h b/ydb/core/resource_pools/regex_predicate.h index 9603600b231..c9cc1ea59b0 100644 --- a/ydb/core/resource_pools/regex_predicate.h +++ b/ydb/core/resource_pools/regex_predicate.h @@ -12,17 +12,15 @@ class TRegExMatch; namespace NKikimr::NResourcePool { /// -/// A compiled regex predicate for classifier matching. -/// -/// Stores both the original user pattern and the compiled regex -/// (anchored as ^(?:pattern)$). Reusable for any regex-typed -/// classifier field (HAS_APP_NAME and future matchers). +/// A compiled predicate for classifier matching. Built from a user-facing +/// glob (`*`, `?` wildcards; other characters — including regex metachars — +/// are literal). /// struct TRegexPredicate { TString Pattern; std::shared_ptr<TRegExMatch> Compiled; - static TRegexPredicate Compile(TStringBuf pattern); + static TRegexPredicate FromGlob(TStringBuf glob); bool Match(const TString& value) const; }; diff --git a/ydb/core/resource_pools/regex_predicate_ut.cpp b/ydb/core/resource_pools/regex_predicate_ut.cpp index c0722f81994..87194c7bcc4 100644 --- a/ydb/core/resource_pools/regex_predicate_ut.cpp +++ b/ydb/core/resource_pools/regex_predicate_ut.cpp @@ -8,49 +8,84 @@ namespace NKikimr { using NResourcePool::TRegexPredicate; -Y_UNIT_TEST_SUITE(TRegexPredicateTest) { +Y_UNIT_TEST_SUITE(TRegexPredicateFromGlob) { - Y_UNIT_TEST(LiteralIsAnchored) { - auto pred = TRegexPredicate::Compile("ydb-ui"); - UNIT_ASSERT(pred.Match("ydb-ui")); - UNIT_ASSERT(!pred.Match("ydb-ui-prod")); - UNIT_ASSERT(!pred.Match("prefix-ydb-ui")); + Y_UNIT_TEST(LiteralGlob) { + auto pred = TRegexPredicate::FromGlob("/Root/db/orders"); + UNIT_ASSERT(pred.Match("/Root/db/orders")); + UNIT_ASSERT(!pred.Match("/Root/db/orders_archive")); + UNIT_ASSERT(!pred.Match("/Root/db")); } - Y_UNIT_TEST(AlternationMatchesExactlyEachBranch) { - auto pred = TRegexPredicate::Compile("ydb-ui|ydb-cli"); - UNIT_ASSERT(pred.Match("ydb-ui")); - UNIT_ASSERT(pred.Match("ydb-cli")); - UNIT_ASSERT(!pred.Match("ydb-ui-prod")); - UNIT_ASSERT(!pred.Match("prefix-ydb-cli")); + Y_UNIT_TEST(StarMatchesAnyCount) { + auto pred = TRegexPredicate::FromGlob("/Root/db/*"); + UNIT_ASSERT(pred.Match("/Root/db/")); + UNIT_ASSERT(pred.Match("/Root/db/orders")); + UNIT_ASSERT(pred.Match("/Root/db/archive/orders/2024")); + UNIT_ASSERT(!pred.Match("/Root/db")); + UNIT_ASSERT(!pred.Match("/Root/other/x")); } - Y_UNIT_TEST(WildcardSuffix) { - auto pred = TRegexPredicate::Compile("ydb-.*"); - UNIT_ASSERT(pred.Match("ydb-ui")); - UNIT_ASSERT(pred.Match("ydb-cli-v2")); - UNIT_ASSERT(!pred.Match("ydb")); - UNIT_ASSERT(!pred.Match("other-ydb-ui")); + Y_UNIT_TEST(QuestionMarkMatchesExactlyOne) { + auto pred = TRegexPredicate::FromGlob("/Root/t?"); + UNIT_ASSERT(pred.Match("/Root/t1")); + UNIT_ASSERT(pred.Match("/Root/t_")); + UNIT_ASSERT(!pred.Match("/Root/t")); + UNIT_ASSERT(!pred.Match("/Root/t12")); } - Y_UNIT_TEST(EmptyInputDoesNotMatchNonEmptyPattern) { - auto pred = TRegexPredicate::Compile("ydb-ui"); - UNIT_ASSERT(!pred.Match("")); + Y_UNIT_TEST(QuestionMarkMatchesSlash) { + auto pred = TRegexPredicate::FromGlob("/Root?db"); + UNIT_ASSERT(pred.Match("/Root/db")); + UNIT_ASSERT(pred.Match("/RootXdb")); + UNIT_ASSERT(!pred.Match("/Rootdb")); } - Y_UNIT_TEST(EmptyPatternThrows) { - UNIT_ASSERT_EXCEPTION(TRegexPredicate::Compile(""), yexception); + Y_UNIT_TEST(StarMatchesSlash) { + auto pred = TRegexPredicate::FromGlob("/Root/*/orders"); + UNIT_ASSERT(pred.Match("/Root/db1/orders")); + UNIT_ASSERT(pred.Match("/Root/tenant/sub/orders")); + UNIT_ASSERT(!pred.Match("/Root/orders")); } - Y_UNIT_TEST(AdminProvidedAnchorsAreHarmless) { - auto pred = TRegexPredicate::Compile("^ydb-ui$"); - UNIT_ASSERT(pred.Match("ydb-ui")); - UNIT_ASSERT(!pred.Match("ydb-ui-prod")); + Y_UNIT_TEST(DoubleStarCollapses) { + auto both = TRegexPredicate::FromGlob("/Root/**"); + auto one = TRegexPredicate::FromGlob("/Root/*"); + UNIT_ASSERT(both.Match("/Root/")); + UNIT_ASSERT(both.Match("/Root/anything")); + UNIT_ASSERT(one.Match("/Root/")); + UNIT_ASSERT(one.Match("/Root/anything")); } - Y_UNIT_TEST(InvalidPatternThrows) { - UNIT_ASSERT_EXCEPTION(TRegexPredicate::Compile("[unclosed"), yexception); - UNIT_ASSERT_EXCEPTION(TRegexPredicate::Compile("(?<bad"), yexception); + Y_UNIT_TEST(MixedStarAndQuestion) { + auto pred = TRegexPredicate::FromGlob("/Root/t?/*"); + UNIT_ASSERT(pred.Match("/Root/t1/anything")); + UNIT_ASSERT(pred.Match("/Root/tX/")); + UNIT_ASSERT(!pred.Match("/Root/t/anything")); + UNIT_ASSERT(!pred.Match("/Root/t12/anything")); + } + + Y_UNIT_TEST(DotIsLiteral) { + auto pred = TRegexPredicate::FromGlob("/Root/foo.bar"); + UNIT_ASSERT(pred.Match("/Root/foo.bar")); + UNIT_ASSERT(!pred.Match("/Root/fooxbar")); + } + + Y_UNIT_TEST(RegexMetacharsAreLiteral) { + auto pred = TRegexPredicate::FromGlob("/Root/.+()[]{}|^$\\"); + UNIT_ASSERT(pred.Match("/Root/.+()[]{}|^$\\")); + UNIT_ASSERT(!pred.Match("/Root/a")); + } + + Y_UNIT_TEST(EmptyThrows) { + UNIT_ASSERT_EXCEPTION(TRegexPredicate::FromGlob(""), yexception); + } + + Y_UNIT_TEST(StarOnlyMatchesAnything) { + auto pred = TRegexPredicate::FromGlob("*"); + UNIT_ASSERT(pred.Match("")); + UNIT_ASSERT(pred.Match("anything")); + UNIT_ASSERT(pred.Match("/Root/db/orders")); } } diff --git a/ydb/core/resource_pools/resource_pool_classifier_settings.cpp b/ydb/core/resource_pools/resource_pool_classifier_settings.cpp index 6bbce7b4376..26df0373ded 100644 --- a/ydb/core/resource_pools/resource_pool_classifier_settings.cpp +++ b/ydb/core/resource_pools/resource_pool_classifier_settings.cpp @@ -30,7 +30,7 @@ void TClassifierSettings::TParser::operator()(std::optional<TRegexPredicate>* se if (Value.empty()) { setting->reset(); } else { - *setting = TRegexPredicate::Compile(Value); + *setting = TRegexPredicate::FromGlob(Value); } } diff --git a/ydb/core/resource_pools/resource_pool_classifier_settings.h b/ydb/core/resource_pools/resource_pool_classifier_settings.h index 9de2bb39e08..71eff8f0ccf 100644 --- a/ydb/core/resource_pools/resource_pool_classifier_settings.h +++ b/ydb/core/resource_pools/resource_pool_classifier_settings.h @@ -43,7 +43,7 @@ struct TClassifierSettings : public TSettingsBase { i64 Rank = -1; // -1 = max rank + CLASSIFIER_RANK_OFFSET TString ResourcePool = DEFAULT_POOL_ID; std::optional<TString> MemberName; - std::optional<TRegexPredicate> HasAppName; + std::optional<TString> HasAppName; std::optional<TRegexPredicate> HasFullScan; std::optional<TRegexPredicate> HasPath; std::optional<EClassifierAction> Action; diff --git a/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp b/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp index dca7debcfaa..be23cb7bb7c 100644 --- a/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp +++ b/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp @@ -37,30 +37,24 @@ Y_UNIT_TEST_SUITE(ResourcePoolClassifierTest) { UNIT_ASSERT_VALUES_EQUAL(settings.MemberName, "test@user"); } - Y_UNIT_TEST(RegexPredicateParsing) { + Y_UNIT_TEST(PredicateParsing) { + // Verify the parser dispatches to the right overload for each predicate's field type. + // Glob semantics themselves live in regex_predicate_ut.cpp. TClassifierSettings settings; auto propertiesMap = settings.GetPropertiesMap(); - std::visit(TClassifierSettings::TParser{"ydb-ui|ydb-cli"}, propertiesMap["has_app_name"]); + std::visit(TClassifierSettings::TParser{"my_app"}, propertiesMap["has_app_name"]); + std::visit(TClassifierSettings::TParser{"/Root/db/orders_*"}, propertiesMap["has_full_scan"]); + std::visit(TClassifierSettings::TParser{"/Root/db/archive/*"}, propertiesMap["has_path"]); + UNIT_ASSERT(settings.HasAppName.has_value()); - UNIT_ASSERT_VALUES_EQUAL(settings.HasAppName->Pattern, "ydb-ui|ydb-cli"); + UNIT_ASSERT_VALUES_EQUAL(*settings.HasAppName, "my_app"); - std::visit(TClassifierSettings::TParser{"/Root/db/orders_.*"}, propertiesMap["has_full_scan"]); UNIT_ASSERT(settings.HasFullScan.has_value()); - UNIT_ASSERT_VALUES_EQUAL(settings.HasFullScan->Pattern, "/Root/db/orders_.*"); + UNIT_ASSERT_VALUES_EQUAL(settings.HasFullScan->Pattern, "/Root/db/orders_*"); - std::visit(TClassifierSettings::TParser{"/Root/db/archive/.*"}, propertiesMap["has_path"]); UNIT_ASSERT(settings.HasPath.has_value()); - UNIT_ASSERT_VALUES_EQUAL(settings.HasPath->Pattern, "/Root/db/archive/.*"); - } - - Y_UNIT_TEST(RegexPredicateInvalidPattern) { - TClassifierSettings settings; - auto propertiesMap = settings.GetPropertiesMap(); - - UNIT_ASSERT_EXCEPTION(std::visit(TClassifierSettings::TParser{"[invalid"}, propertiesMap["has_app_name"]), yexception); - UNIT_ASSERT_EXCEPTION(std::visit(TClassifierSettings::TParser{"[invalid"}, propertiesMap["has_full_scan"]), yexception); - UNIT_ASSERT_EXCEPTION(std::visit(TClassifierSettings::TParser{"[invalid"}, propertiesMap["has_path"]), yexception); + UNIT_ASSERT_VALUES_EQUAL(settings.HasPath->Pattern, "/Root/db/archive/*"); } Y_UNIT_TEST(SettingsExtracting) { @@ -76,22 +70,22 @@ Y_UNIT_TEST_SUITE(ResourcePoolClassifierTest) { UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["member_name"]), "test@user"); } - Y_UNIT_TEST(RegexPredicateExtracting) { + Y_UNIT_TEST(PredicateRoundTripExtracting) { TClassifierSettings settings; auto propertiesMap = settings.GetPropertiesMap(); - // Parse then extract — must round-trip the original pattern - std::visit(TClassifierSettings::TParser{"ydb-.*"}, propertiesMap["has_app_name"]); - std::visit(TClassifierSettings::TParser{"/Root/db/orders_.*"}, propertiesMap["has_full_scan"]); - std::visit(TClassifierSettings::TParser{"/Root/db/archive/.*"}, propertiesMap["has_path"]); + // Parse then extract — must round-trip the raw user string (literal / glob). + std::visit(TClassifierSettings::TParser{"ydb-cli"}, propertiesMap["has_app_name"]); + std::visit(TClassifierSettings::TParser{"/Root/db/orders_*"}, propertiesMap["has_full_scan"]); + std::visit(TClassifierSettings::TParser{"/Root/db/archive/*"}, propertiesMap["has_path"]); TClassifierSettings::TExtractor extractor; - UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["has_app_name"]), "ydb-.*"); - UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["has_full_scan"]), "/Root/db/orders_.*"); - UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["has_path"]), "/Root/db/archive/.*"); + UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["has_app_name"]), "ydb-cli"); + UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["has_full_scan"]), "/Root/db/orders_*"); + UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["has_path"]), "/Root/db/archive/*"); } - Y_UNIT_TEST(RegexPredicateExtractingEmpty) { + Y_UNIT_TEST(PredicateExtractingEmpty) { TClassifierSettings settings; auto propertiesMap = settings.GetPropertiesMap(); |
