#include #include #include #include #include #include using namespace yams::search; namespace { std::vector sorted(std::vector v) { std::sort(v.begin(), v.end()); return v; } } // namespace TEST_CASE("QueryLiteralExtractor::extract() with null AST", "[search][literal_extractor][catch2]") { const QueryNode* ast = nullptr; const auto result = QueryLiteralExtractor::extract(ast); REQUIRE(result.terms.empty()); REQUIRE(result.hasPureTerms == false); REQUIRE(result.mostSelective.empty()); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(1.0f, 1e-6f)); } TEST_CASE("QueryLiteralExtractor::extract() with single TermNode", "[search][literal_extractor][catch2]") { TermNode term("hello"); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE(result.terms == std::vector{"hello"}); REQUIRE(result.hasPureTerms == true); REQUIRE(result.mostSelective == "hello"); REQUIRE(result.estimatedSelectivity >= 0.0f); REQUIRE(result.estimatedSelectivity <= 1.0f); } TEST_CASE("QueryLiteralExtractor::extract() with PhraseNode", "[search][literal_extractor][catch2]") { PhraseNode phrase("exact match phrase"); const auto result = QueryLiteralExtractor::extract(&phrase); REQUIRE(result.hasPureTerms == true); REQUIRE(result.terms == std::vector{"exact match phrase"}); } TEST_CASE("QueryLiteralExtractor::extract() with AndNode", "[search][literal_extractor][catch2]") { auto ast = std::make_unique(std::make_unique("foo"), std::make_unique("SpecificClassName")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.hasPureTerms == true); REQUIRE(sorted(result.terms) == sorted(std::vector{"foo", "SpecificClassName"})); REQUIRE(result.mostSelective == "SpecificClassName"); } TEST_CASE("QueryLiteralExtractor::extract() with OrNode", "[search][literal_extractor][catch2]") { auto ast = std::make_unique(std::make_unique("alpha"), std::make_unique("beta")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.hasPureTerms == true); REQUIRE(sorted(result.terms) == sorted(std::vector{"alpha", "beta"})); } TEST_CASE("QueryLiteralExtractor::extract() with NotNode skips terms", "[search][literal_extractor][catch2]") { auto ast = std::make_unique(std::make_unique("secret")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.terms.empty()); REQUIRE(result.hasPureTerms == false); REQUIRE(result.mostSelective.empty()); } TEST_CASE("QueryLiteralExtractor::extract() with FieldNode", "[search][literal_extractor][catch2]") { auto ast = std::make_unique("author", std::make_unique("john")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.hasPureTerms == true); REQUIRE(result.terms == std::vector{"john"}); REQUIRE(result.mostSelective == "john"); } TEST_CASE("QueryLiteralExtractor::extract() with GroupNode", "[search][literal_extractor][catch2]") { auto ast = std::make_unique(std::make_unique("grouped")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.hasPureTerms == true); REQUIRE(result.terms == std::vector{"grouped"}); } TEST_CASE("QueryLiteralExtractor::extract() skips Wildcard/Fuzzy/Range nodes", "[search][literal_extractor][catch2]") { SECTION("WildcardNode") { WildcardNode node("foo*"); const auto result = QueryLiteralExtractor::extract(&node); REQUIRE(result.terms.empty()); REQUIRE(result.hasPureTerms == false); } SECTION("FuzzyNode") { FuzzyNode node("hello", 2); const auto result = QueryLiteralExtractor::extract(&node); REQUIRE(result.terms.empty()); REQUIRE(result.hasPureTerms == false); } SECTION("RangeNode") { RangeNode node("date", "2020", "2021"); const auto result = QueryLiteralExtractor::extract(&node); REQUIRE(result.terms.empty()); REQUIRE(result.hasPureTerms == false); } } TEST_CASE("QueryLiteralExtractor scoring influences mostSelective", "[search][literal_extractor][catch2]") { SECTION("Longer terms score higher") { auto ast = std::make_unique(std::make_unique("tiny"), std::make_unique("muchlonger")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.mostSelective == "muchlonger"); } SECTION("Uppercase terms score higher") { auto ast = std::make_unique(std::make_unique("classname"), std::make_unique("ClassName")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.mostSelective == "ClassName"); } SECTION("Special characters increase score") { auto ast = std::make_unique(std::make_unique("abcdefg"), std::make_unique("abc-def")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.mostSelective == "abc-def"); } SECTION("Common short words are penalized") { auto ast = std::make_unique(std::make_unique("the"), std::make_unique("abc")); const auto result = QueryLiteralExtractor::extract(ast.get()); REQUIRE(result.mostSelective == "abc"); } } TEST_CASE("QueryLiteralExtractor::estimateSelectivity via extract()", "[search][literal_extractor][catch2]") { SECTION("Empty term -> 1.0") { TermNode term(""); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(1.0f, 1e-6f)); } SECTION("Short term (<3 chars) -> 1.0") { TermNode term("hi"); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(1.0f, 1e-6f)); } SECTION("Medium term (3-5 chars) -> 0.5") { TermNode term("hello"); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(0.5f, 1e-6f)); } SECTION("Long term (6-9 chars) -> 0.3") { TermNode term("bananas"); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(0.3f, 1e-6f)); } SECTION("Very long term (10+ chars) -> 0.1") { TermNode term("0123456789"); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(0.1f, 1e-6f)); } SECTION("Uppercase reduces selectivity by 0.7x") { TermNode term("Hello"); const auto result = QueryLiteralExtractor::extract(&term); REQUIRE_THAT(result.estimatedSelectivity, Catch::Matchers::WithinAbs(0.35f, 1e-6f)); } }