diff options
Diffstat (limited to 'searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp')
-rw-r--r-- | searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp | 349 |
1 files changed, 296 insertions, 53 deletions
diff --git a/searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp b/searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp index fe9f6424b9c..ecb7ee3b48b 100644 --- a/searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp +++ b/searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp @@ -2,18 +2,28 @@ #include <vespa/searchlib/queryeval/simpleresult.h> #include <vespa/searchlib/queryeval/blueprint.h> +#include <vespa/searchlib/queryeval/intermediate_blueprints.h> #include <vespa/searchlib/queryeval/leaf_blueprints.h> +#include <vespa/searchlib/queryeval/isourceselector.h> +#include <vespa/searchlib/queryeval/simple_phrase_blueprint.h> +#include <vespa/searchlib/queryeval/equiv_blueprint.h> +#include <vespa/searchlib/queryeval/weighted_set_term_blueprint.h> +#include <vespa/searchlib/queryeval/dot_product_blueprint.h> +#include <vespa/searchlib/queryeval/same_element_blueprint.h> +#include <vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.h> +#include <vespa/searchlib/fef/matchdatalayout.h> #include <vespa/vespalib/util/trinary.h> #include <vespa/vespalib/util/require.h> #include <vespa/vespalib/gtest/gtest.h> #include <functional> -using search::queryeval::AlwaysTrueBlueprint; -using search::queryeval::Blueprint; -using search::queryeval::EmptyBlueprint; -using search::queryeval::SimpleBlueprint; -using search::queryeval::SimpleResult; -using search::queryeval::SearchIterator; +namespace search::fef { class TermFieldMatchDataArray; } +namespace search::fef { class MatchData; } + +using namespace search::queryeval; +using search::fef::MatchData; +using search::fef::MatchDataLayout; +using search::fef::TermFieldMatchDataArray; using vespalib::Trinary; using Constraint = Blueprint::FilterConstraint; @@ -27,37 +37,48 @@ concept FilterFactory = requires(const T &a, bool strict, Constraint upper_or_lo { a.createFilterSearch(strict, upper_or_lower) } -> std::same_as<std::unique_ptr<SearchIterator>>; }; -using factory_fun = std::function<std::unique_ptr<SearchIterator>(const std::vector<Blueprint*> &, bool, Constraint)>; +template <typename T> +concept FilterFactoryBuilder = requires(T a, std::unique_ptr<Blueprint> bp) { + { a.add(std::move(bp)) } -> std::same_as<T&>; +}; -// Combine children blueprints using a shared filter creation -// algorithm. Satisfies the FilterFactory concept. -struct Combine { - factory_fun fun; - std::vector<Blueprint*> list; - Combine(factory_fun fun_in) : fun(fun_in), list() {} - Combine &&add(std::unique_ptr<Blueprint> child) && { - list.push_back(child.release()); - return std::move(*this); - } - auto createFilterSearch(bool strict, Constraint upper_or_lower) const { - return fun(list, strict, upper_or_lower); +template <typename T> +concept ChildCollector = requires(T a, std::unique_ptr<Blueprint> bp) { + a.addChild(std::move(bp)); +}; + +// inherit Blueprint to capture the default filter factory +struct DefaultBlueprint : Blueprint { + void optimize(Blueprint* &) override { abort(); } + const State &getState() const override { abort(); } + void fetchPostings(const ExecuteInfo &) override { abort(); } + void freeze() override { abort(); } + SearchIteratorUP createSearch(MatchData &, bool) const override { abort(); } +}; + +// add the use of a field to a leaf blueprint (SimplePhraseBlueprint asserts on this) +struct FakeFieldProxy : SimpleLeafBlueprint { + std::unique_ptr<Blueprint> child; + FakeFieldProxy(const FieldSpec &field, std::unique_ptr<Blueprint> child_in) + : SimpleLeafBlueprint(field), child(std::move(child_in)) + { + setParent(child->getParent()); + child->setParent(this); } - ~Combine() { - for (auto *ptr: list) { - delete ptr; - } + SearchIteratorUP createLeafSearch(const TermFieldMatchDataArray &, bool) const override { abort(); } + SearchIteratorUP createFilterSearch(bool strict, Constraint upper_or_lower) const override { + return child->createFilterSearch(strict, upper_or_lower); } }; -// create a leaf blueprint that matches no documents -std::unique_ptr<Blueprint> empty() { - return std::make_unique<EmptyBlueprint>(); -} - -// create a leaf blueprint that matches all documents -std::unique_ptr<Blueprint> full() { - return std::make_unique<AlwaysTrueBlueprint>(); -} +// need one of these to be able to create a SourceBlender +struct NullSelector : ISourceSelector { + NullSelector() : ISourceSelector(7) {} + void setSource(uint32_t, Source) override { abort(); } + uint32_t getDocIdLimit() const override { abort(); } + void compactLidSpace(uint32_t) override { abort(); } + std::unique_ptr<sourceselector::Iterator> createIterator() const override { abort(); } +}; // make a simple result containing the given documents SimpleResult make_result(const std::vector<uint32_t> &docs) { @@ -82,25 +103,205 @@ SimpleResult make_empty_result() { return SimpleResult(); } +// create a leaf blueprint that matches no documents +std::unique_ptr<Blueprint> empty() { + return std::make_unique<EmptyBlueprint>(); +} + +// create a leaf blueprint that matches all documents +std::unique_ptr<Blueprint> full() { + return std::make_unique<AlwaysTrueBlueprint>(); +} + // create a leaf blueprint with the specified hits -std::unique_ptr<Blueprint> leaf(const std::vector<uint32_t> &docs) { +std::unique_ptr<Blueprint> hits(const std::vector<uint32_t> &docs) { return std::make_unique<SimpleBlueprint>(make_result(docs)); } +// Describes blueprint children with a list of simple factories that +// can later be used to create them. +struct Children { + using Factory = std::function<Blueprint::UP()>; + std::vector<Factory> list; + Children() : list() {} + size_t size() const { return list.size(); } + Children &hits(const std::vector<uint32_t> &docs) { + list.push_back([docs](){ return ::hits(docs); }); + return *this; + } + Children &full() { + list.push_back([](){ return ::full(); }); + return *this; + } + Children &empty() { + list.push_back([](){ return ::empty(); }); + return *this; + } + template <FilterFactoryBuilder Builder> + Builder &apply(Builder &builder) const { + for (const Factory &make_child: list) { + builder.add(make_child()); + } + return builder; + } +}; + +// Combine children blueprints using a shared filter creation +// algorithm. Satisfies the FilterFactory concept. +struct Combine { + using factory_fun = std::function<std::unique_ptr<SearchIterator>(const Blueprint::Children &, bool, Constraint)>; + factory_fun fun; + Blueprint::Children list; + Combine(factory_fun fun_in) noexcept : fun(fun_in), list() {} + Combine &add(std::unique_ptr<Blueprint> child) { + list.push_back(std::move(child)); + return *this; + } + Combine &add(const Children &children) { + return children.apply(*this); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return fun(list, strict, upper_or_lower); + } + ~Combine(); +}; +Combine::~Combine() = default; + +// enable Make-ing source blender +struct SourceBlenderAdapter { + NullSelector selector; + SourceBlenderBlueprint blueprint; + SourceBlenderAdapter() : selector(), blueprint(selector) {} + void addChild(std::unique_ptr<Blueprint> child) { + blueprint.addChild(std::move(child)); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +// enable Make-ing simple phrase +struct SimplePhraseAdapter { + FieldSpec field; + SimplePhraseBlueprint blueprint; + SimplePhraseAdapter() : field("foo", 3, 7), blueprint(field, false) {} + void addChild(std::unique_ptr<Blueprint> child) { + auto child_field = blueprint.getNextChildField(field); + auto term = std::make_unique<FakeFieldProxy>(child_field, std::move(child)); + blueprint.addTerm(std::move(term)); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +//enable Make-ing equiv +struct EquivAdapter { + FieldSpecBaseList fields; + EquivBlueprint blueprint; + EquivAdapter() : fields(), blueprint(fields, MatchDataLayout()) {} + void addChild(std::unique_ptr<Blueprint> child) { + blueprint.addTerm(std::move(child), 1.0); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +// enable Make-ing weighted set +struct WeightedSetTermAdapter { + FieldSpec field; + WeightedSetTermBlueprint blueprint; + WeightedSetTermAdapter() : field("foo", 3, 7), blueprint(field) {} + void addChild(std::unique_ptr<Blueprint> child) { + blueprint.addTerm(std::move(child), 100); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +// enable Make-ing dot product +struct DotProductAdapter { + FieldSpec field; + DotProductBlueprint blueprint; + DotProductAdapter() : field("foo", 3, 7), blueprint(field) {} + void addChild(std::unique_ptr<Blueprint> child) { + auto child_field = blueprint.getNextChildField(field); + auto term = std::make_unique<FakeFieldProxy>(child_field, std::move(child)); + blueprint.addTerm(std::move(term), 100); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +// enable Make-ing parallel weak and +struct ParallelWeakAndAdapter { + FieldSpec field; + ParallelWeakAndBlueprint blueprint; + ParallelWeakAndAdapter() : field("foo", 3, 7), blueprint(field, 100, 0.0, 1.0) {} + void addChild(std::unique_ptr<Blueprint> child) { + auto child_field = blueprint.getNextChildField(field); + auto term = std::make_unique<FakeFieldProxy>(child_field, std::move(child)); + blueprint.addTerm(std::move(term), 100); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +// enable Make-ing same element +struct SameElementAdapter { + SameElementBlueprint blueprint; + SameElementAdapter() : blueprint("foo", false) {} + void addChild(std::unique_ptr<Blueprint> child) { + auto child_field = blueprint.getNextChildField("foo", 3); + auto term = std::make_unique<FakeFieldProxy>(child_field, std::move(child)); + blueprint.addTerm(std::move(term)); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + +// Make a specific intermediate-ish blueprint that you can add +// children to. Satisfies the FilterFactory concept. +template <FilterFactory T> +requires ChildCollector<T> +struct Make { + T blueprint; + template <typename ... Args> + Make(Args && ... args) : blueprint(std::forward<Args>(args)...) {} + Make &add(std::unique_ptr<Blueprint> child) { + blueprint.addChild(std::move(child)); + return *this; + } + Make &add(const Children &children) { + return children.apply(*this); + } + auto createFilterSearch(bool strict, Constraint upper_or_lower) const { + return blueprint.createFilterSearch(strict, upper_or_lower); + } +}; + // what kind of results are we expecting from a filter search? struct Expect { Trinary matches_any; - SimpleResult hits; - Expect(const std::vector<uint32_t> &hits_in) - : matches_any(Trinary::Undefined), hits(make_result(hits_in)) {} - Expect(Trinary matches_any_in) : matches_any(matches_any_in), hits() { + SimpleResult docs; + Expect(const std::vector<uint32_t> &docs_in) + : matches_any(Trinary::Undefined), docs(make_result(docs_in)) {} + Expect(Trinary matches_any_in) : matches_any(matches_any_in), docs() { REQUIRE(matches_any != Trinary::Undefined); if (matches_any == Trinary::True) { - hits = make_full_result(); + docs = make_full_result(); } else { - hits = make_empty_result(); + docs = make_empty_result(); } } + static Expect empty() { return Expect(Trinary::False); } + static Expect full() { return Expect(Trinary::True); } + static Expect hits(const std::vector<uint32_t> &docs) { return Expect(docs); } }; template <FilterFactory Blueprint> @@ -116,7 +317,7 @@ void verify(const Blueprint &blueprint, const Expect &upper, const Expect &lower } else { actual.search(*filter, docid_limit); } - EXPECT_EQ(actual, expect.hits); + EXPECT_EQ(actual, expect.docs); } } } @@ -127,31 +328,73 @@ void verify(const Blueprint &blueprint, const Expect &upper_and_lower) { } TEST(FilterSearchTest, empty_leaf) { - verify(*empty(), Expect(Trinary::False)); + verify(*empty(), Expect::empty()); } TEST(FilterSearchTest, full_leaf) { - verify(*full(), Expect(Trinary::True)); + verify(*full(), Expect::full()); } TEST(FilterSearchTest, custom_leaf) { - verify(*leaf({5,10,20}), Expect({5,10,20})); + verify(*hits({5,10,20}), Expect::hits({5,10,20})); +} + +TEST(FilterSearchTest, default_blueprint) { + verify(DefaultBlueprint(), Expect::full(), Expect::empty()); } TEST(FilterSearchTest, simple_or) { - verify(Combine(Blueprint::create_or_filter) - .add(leaf({5, 10})) - .add(leaf({7})) - .add(leaf({3, 11})), - Expect({3, 5, 7, 10, 11})); + auto child_list = Children() + .hits({5, 10}) + .hits({7}) + .hits({3, 11}); + auto expected = Expect::hits({3, 5, 7, 10, 11}); + verify(Combine(Blueprint::create_or_filter).add(child_list), expected); + verify(Make<OrBlueprint>().add(child_list), expected); + verify(Make<EquivAdapter>().add(child_list), expected); + verify(Make<WeightedSetTermAdapter>().add(child_list), expected); + verify(Make<DotProductAdapter>().add(child_list), expected); + verify(Combine(Blueprint::create_atmost_or_filter).add(child_list), expected, Expect::empty()); + verify(Make<WeakAndBlueprint>(100).add(child_list), expected, Expect::empty()); + verify(Make<SourceBlenderAdapter>().add(child_list), expected, Expect::empty()); + verify(Make<ParallelWeakAndAdapter>().add(child_list), expected, Expect::empty()); } TEST(FilterSearchTest, simple_and) { - verify(Combine(Blueprint::create_and_filter) - .add(leaf({1, 2, 3, 4, 5, 6})) - .add(leaf({2, 4, 6, 7})) - .add(leaf({1, 4, 6, 7, 10})), - Expect({4, 6})); + auto child_list = Children() + .hits({1, 2, 3, 4, 5, 6}) + .hits({2, 4, 6, 7}) + .hits({1, 4, 6, 7, 10}); + auto expected = Expect::hits({4, 6}); + verify(Combine(Blueprint::create_and_filter).add(child_list), expected); + verify(Make<AndBlueprint>().add(child_list), expected); + verify(Combine(Blueprint::create_atmost_and_filter).add(child_list), expected, Expect::empty()); + verify(Make<NearBlueprint>(3).add(child_list), expected, Expect::empty()); + verify(Make<ONearBlueprint>(3).add(child_list), expected, Expect::empty()); + verify(Make<SimplePhraseAdapter>().add(child_list), expected, Expect::empty()); + verify(Make<SameElementAdapter>().add(child_list), expected, Expect::empty()); +} + +TEST(FilterSearchTest, simple_andnot) { + auto child_list = Children() + .hits({1, 2, 3, 4, 5, 6}) + .hits({2, 4, 6}) + .hits({4, 6, 7}); + auto expected = Expect::hits({1, 3, 5}); + verify(Combine(Blueprint::create_andnot_filter).add(child_list), expected); + verify(Make<AndNotBlueprint>().add(child_list), expected); +} + +TEST(FilterSearchTest, rank_filter) { + auto child_list1 = Children().hits({1,2,3}).empty().full(); + auto child_list2 = Children().empty().hits({1,2,3}).full(); + auto child_list3 = Children().full().hits({1,2,3}).empty(); + verify(Combine(Blueprint::create_first_child_filter).add(child_list1), Expect::hits({1,2,3})); + verify(Combine(Blueprint::create_first_child_filter).add(child_list2), Expect::empty()); + verify(Combine(Blueprint::create_first_child_filter).add(child_list3), Expect::full()); + verify(Make<RankBlueprint>().add(child_list1), Expect::hits({1,2,3})); + verify(Make<RankBlueprint>().add(child_list2), Expect::empty()); + verify(Make<RankBlueprint>().add(child_list3), Expect::full()); } GTEST_MAIN_RUN_ALL_TESTS() |