summaryrefslogtreecommitdiffstats
path: root/searchlib/src/tests/queryeval/filter_search/filter_search_test.cpp
diff options
context:
space:
mode:
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.cpp349
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()