diff options
author | Håvard Pettersen <havardpe@oath.com> | 2019-10-08 15:19:27 +0000 |
---|---|---|
committer | Håvard Pettersen <havardpe@oath.com> | 2019-10-08 15:25:56 +0000 |
commit | 0f21137ac41a01ce9a77460b67d96fe7d76c2983 (patch) | |
tree | 00447d787793b5092ffe827ccf776bbf17e7bf1d | |
parent | 60f301c40a1a57634247f3d240fdd1f2341a4420 (diff) |
improve fake searchable attributes
blueprint/search iterator now exposes a more functional attribute
search context
fake attribute searches will now unpack a single entry containing the
sum of all matched weights
improve matcher test for same element matching by also using the
attribute element iterator
added tests for identifying matching elements in docsum request
10 files changed, 297 insertions, 205 deletions
diff --git a/searchcore/src/tests/proton/matching/matching_test.cpp b/searchcore/src/tests/proton/matching/matching_test.cpp index 950321533b0..05ca9e758ab 100644 --- a/searchcore/src/tests/proton/matching/matching_test.cpp +++ b/searchcore/src/tests/proton/matching/matching_test.cpp @@ -91,7 +91,7 @@ vespalib::string make_simple_stack_dump(const vespalib::string &field, const ves vespalib::string make_same_element_stack_dump(const vespalib::string &a1_term, const vespalib::string &f1_term) { QueryBuilder<ProtonNodeTypes> builder; - builder.addSameElement(2, ""); + builder.addSameElement(2, "my"); builder.addStringTerm(a1_term, "a1", 1, search::query::Weight(1)); builder.addStringTerm(f1_term, "f1", 2, search::query::Weight(1)); return StackDumpCreator::create(*builder.build()); @@ -129,10 +129,12 @@ struct MyWorld { schema.addIndexField(Schema::IndexField("f1", DataType::STRING)); schema.addIndexField(Schema::IndexField("f2", DataType::STRING)); schema.addIndexField(Schema::IndexField("tensor_field", DataType::TENSOR)); + schema.addIndexField(Schema::IndexField("my.f1", DataType::STRING)); schema.addAttributeField(Schema::AttributeField("a1", DataType::INT32)); schema.addAttributeField(Schema::AttributeField("a2", DataType::INT32)); schema.addAttributeField(Schema::AttributeField("a3", DataType::INT32)); schema.addAttributeField(Schema::AttributeField("predicate_field", DataType::BOOLEANTREE)); + schema.addAttributeField(Schema::AttributeField("my.a1", DataType::STRING)); // config config.add(indexproperties::rank::FirstPhase::NAME, "attribute(a1)"); @@ -237,11 +239,11 @@ struct MyWorld { searchContext.attr().addResult("a1", term, result); } - void add_same_element_results(const vespalib::string &a1_term, const vespalib::string &f1_0_term) { - auto a1_result = make_elem_result({{10, {1}}, {20, {2}}, {21, {2}}}); - auto f1_0_result = make_elem_result({{10, {2}}, {20, {2}}, {21, {2}}}); - searchContext.attr().addResult("a1", a1_term, a1_result); - searchContext.idx(0).getFake().addResult("f1", f1_0_term, f1_0_result); + void add_same_element_results(const vespalib::string &my_a1_term, const vespalib::string &my_f1_0_term) { + auto my_a1_result = make_elem_result({{10, {1}}, {20, {2, 3}}, {21, {2}}}); + auto my_f1_0_result = make_elem_result({{10, {2}}, {20, {1, 2}}, {21, {2}}}); + searchContext.attr().addResult("my.a1", my_a1_term, my_a1_result); + searchContext.idx(0).getFake().addResult("my.f1", my_f1_0_term, my_f1_0_result); } void basicResults() { @@ -323,20 +325,20 @@ struct MyWorld { return reply; } - DocsumRequest::SP createSimpleDocsumRequest(const vespalib::string & field, const vespalib::string & term) - { - DocsumRequest::SP request(new DocsumRequest); - setStackDump(*request, make_simple_stack_dump(field, term)); + DocsumRequest::UP create_docsum_request(const vespalib::string &stack_dump, const std::initializer_list<uint32_t> docs) { + auto req = std::make_unique<DocsumRequest>(); + setStackDump(*req, stack_dump); + for (uint32_t docid: docs) { + req->hits.push_back(DocsumRequest::Hit()); + req->hits.back().docid = docid; + } + return req; + } + DocsumRequest::SP createSimpleDocsumRequest(const vespalib::string & field, const vespalib::string & term) { // match a subset of basic result + request for a non-hit (not // sorted on docid) - request->hits.push_back(DocsumRequest::Hit()); - request->hits.back().docid = 30; - request->hits.push_back(DocsumRequest::Hit()); - request->hits.back().docid = 10; - request->hits.push_back(DocsumRequest::Hit()); - request->hits.back().docid = 15; - return request; + return create_docsum_request(make_simple_stack_dump(field, term), {30, 10, 15}); } std::unique_ptr<FieldInfo> get_field_info(const vespalib::string &field_name) { @@ -350,14 +352,21 @@ struct MyWorld { FeatureSet::SP getSummaryFeatures(DocsumRequest::SP req) { Matcher::SP matcher = createMatcher(); - return matcher->getSummaryFeatures(*req, searchContext, attributeContext, *sessionManager); + auto docsum_matcher = matcher->create_docsum_matcher(*req, searchContext, attributeContext, *sessionManager); + return docsum_matcher->get_summary_features(); } FeatureSet::SP getRankFeatures(DocsumRequest::SP req) { Matcher::SP matcher = createMatcher(); - return matcher->getRankFeatures(*req, searchContext, attributeContext, *sessionManager); + auto docsum_matcher = matcher->create_docsum_matcher(*req, searchContext, attributeContext, *sessionManager); + return docsum_matcher->get_rank_features(); } + MatchingElements::UP get_matching_elements(const DocsumRequest &req, const StructFieldMapper &mapper) { + Matcher::SP matcher = createMatcher(); + auto docsum_matcher = matcher->create_docsum_matcher(req, searchContext, attributeContext, *sessionManager); + return docsum_matcher->get_matching_elements(mapper); + } }; MyWorld::MyWorld() @@ -898,7 +907,7 @@ TEST("require that fields are tagged with data type") { EXPECT_EQUAL(predicate_field->get_data_type(), FieldInfo::DataType::BOOLEANTREE); } -TEST("require that same element search works (note that this does not test/use the attribute element iterator wrapper)") { +TEST("require that same element search works") { MyWorld world; world.basicSetup(); world.add_same_element_results("foo", "bar"); @@ -908,4 +917,33 @@ TEST("require that same element search works (note that this does not test/use t EXPECT_EQUAL(document::DocumentId("id:ns:searchdocument::20").getGlobalId(), reply->hits[0].gid); } +TEST("require that docsum matcher can extract matching elements from same element blueprint") { + MyWorld world; + world.basicSetup(); + world.add_same_element_results("foo", "bar"); + auto request = world.create_docsum_request(make_same_element_stack_dump("foo", "bar"), {20}); + StructFieldMapper mapper; + mapper.add_mapping("my", "my.a1"); + mapper.add_mapping("my", "my.f1"); + auto result = world.get_matching_elements(*request, mapper); + const auto &list = result->get_matching_elements(20, "my"); + ASSERT_EQUAL(list.size(), 1u); + EXPECT_EQUAL(list[0], 2u); +} + +TEST("require that docsum matcher can extract matching elements from single attribute term") { + MyWorld world; + world.basicSetup(); + world.add_same_element_results("foo", "bar"); + auto request = world.create_docsum_request(make_simple_stack_dump("my.a1", "foo"), {20}); + StructFieldMapper mapper; + mapper.add_mapping("my", "my.a1"); + mapper.add_mapping("my", "my.f1"); + auto result = world.get_matching_elements(*request, mapper); + const auto &list = result->get_matching_elements(20, "my"); + ASSERT_EQUAL(list.size(), 2u); + EXPECT_EQUAL(list[0], 2u); + EXPECT_EQUAL(list[1], 3u); +} + TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchcore/src/vespa/searchcore/proton/matching/fakesearchcontext.cpp b/searchcore/src/vespa/searchcore/proton/matching/fakesearchcontext.cpp index 11cf2713528..36129640c0e 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/fakesearchcontext.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/fakesearchcontext.cpp @@ -12,7 +12,9 @@ FakeSearchContext::FakeSearchContext(size_t initialNumDocs) _indexes(new IndexCollection(_selector)), _attrSearchable(), _docIdLimit(initialNumDocs) -{} +{ + _attrSearchable.is_attr(true); +} FakeSearchContext::~FakeSearchContext() {} diff --git a/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt b/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt index e5f5e6aadf3..c98306aa179 100644 --- a/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt +++ b/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt @@ -4,5 +4,6 @@ vespa_add_executable(searchlib_fake_searchable_test_app TEST fake_searchable_test.cpp DEPENDS searchlib + gtest ) vespa_add_test(NAME searchlib_fake_searchable_test_app COMMAND searchlib_fake_searchable_test_app) diff --git a/searchlib/src/tests/queryeval/fake_searchable/fake_searchable_test.cpp b/searchlib/src/tests/queryeval/fake_searchable/fake_searchable_test.cpp index 661ca9c2ba3..6cef4479439 100644 --- a/searchlib/src/tests/queryeval/fake_searchable/fake_searchable_test.cpp +++ b/searchlib/src/tests/queryeval/fake_searchable/fake_searchable_test.cpp @@ -1,5 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/vespalib/testkit/testapp.h> + +#include <vespa/vespalib/gtest/gtest.h> #include <vespa/searchlib/queryeval/fake_searchable.h> #include <vespa/searchlib/queryeval/fake_requestcontext.h> @@ -8,76 +9,58 @@ #include <vespa/searchlib/query/tree/simplequery.h> #include <vespa/searchlib/fef/matchdata.h> -#include <vespa/log/log.h> -LOG_SETUP("fake_searchable_test"); - using namespace search::queryeval; using namespace search::query; using namespace search::fef; -class Test : public vespalib::TestApp { -public: - ~Test(); - int Main() override; - void testTestFakeResult(); - void testTerm(); - void testPhrase(); - void testWeightedSet(); - void testMultiField(); - void testPhraseWithEmptyChild(); -private: - FakeRequestContext _requestContext; +struct FakeSearchableTest : ::testing::Test { + Weight w; + FakeRequestContext req_ctx; + FakeSearchable source; + FakeSearchableTest() : w(100), req_ctx(), source() {} }; -Test::~Test() {} -void -Test::testTestFakeResult() -{ - EXPECT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).pos(6).elem(6).doc(6), - FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).pos(6).elem(6).doc(6)); +TEST(FakeResultTest, require_that_fake_result_works) { + EXPECT_EQ(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).pos(6).elem(6).doc(6), + FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).pos(6).elem(6).doc(6)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(1).elem(5).len(15).weight(5).pos(5)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(1).elem(5).len(15).weight(5).pos(5)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(1).len(15).weight(5).pos(5)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(1).len(15).weight(5).pos(5)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(5).len(19).weight(5).pos(5)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(5).len(19).weight(5).pos(5)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(5).len(15).weight(1).pos(5)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(5).len(15).weight(1).pos(5)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(5).len(15).weight(5).pos(1)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(5).len(15).weight(5).pos(1)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).doc(6)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).doc(6)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).elem(6)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).elem(6)); - EXPECT_NOT_EQUAL(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5), - FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).pos(6)); + EXPECT_FALSE(FakeResult().doc(5).elem(5).len(15).weight(5).pos(5) == + FakeResult().doc(5).elem(5).len(15).weight(5).pos(5).pos(6)); } -void -Test::testTerm() -{ - Weight w(100); - - FakeSearchable source; +TEST_F(FakeSearchableTest, require_that_term_search_works) { source.addResult("fieldfoo", "word1", - FakeResult().doc(5).pos(3)); + FakeResult().doc(5).elem(2).pos(3).elem(4).pos(5)); SimpleStringTerm termNode("word1", "viewfoo", 1, w); FieldSpecList fields; fields.add(FieldSpec("fieldfoo", 1, 1)); - Blueprint::UP bp = source.createBlueprint(_requestContext, fields, termNode); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, termNode); for (int i = 0; i <= 1; ++i) { bool strict = (i == 0); - TEST_STATE(strict ? "strict" : "non-strict"); + SCOPED_TRACE(strict ? "strict" : "non-strict"); MatchData::UP md = MatchData::makeTestInstance(100, 10); bp->fetchPostings(strict); SearchIterator::UP search = bp->createSearch(*md, strict); @@ -85,21 +68,26 @@ Test::testTerm() EXPECT_TRUE(!search->seek(3)); if (strict) { - EXPECT_EQUAL(5u, search->getDocId()); + EXPECT_EQ(5u, search->getDocId()); } else { EXPECT_TRUE(search->seek(5u)); } - EXPECT_EQUAL(5u, search->getDocId()); + EXPECT_EQ(5u, search->getDocId()); { // test doc 5 results search->unpack(5u); { TermFieldMatchData &data = *md->resolveTermField(1); - EXPECT_EQUAL(1u, data.getFieldId()); - EXPECT_EQUAL(5u, data.getDocId()); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_EQ(5u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(1u, itr.size()); + EXPECT_EQ(2u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(3u, itr.getPosition()); + EXPECT_EQ(2u, itr.getElementId()); + EXPECT_EQ(3u, itr.getPosition()); + itr.next(); + ASSERT_TRUE(itr.valid()); + EXPECT_EQ(4u, itr.getElementId()); + EXPECT_EQ(5u, itr.getPosition()); itr.next(); EXPECT_TRUE(!itr.valid()); } @@ -111,12 +99,7 @@ Test::testTerm() } } -void -Test::testPhrase() -{ - Weight w(100); - - FakeSearchable source; +TEST_F(FakeSearchableTest, require_that_phrase_search_works) { source.addResult("fieldfoo", "word1", FakeResult().doc(3).pos(7).doc(5).pos(3)); source.addResult("fieldfoo", "word2", @@ -128,10 +111,10 @@ Test::testPhrase() FieldSpecList fields; fields.add(FieldSpec("fieldfoo", 1, 1)); - Blueprint::UP bp = source.createBlueprint(_requestContext, fields, phraseNode); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, phraseNode); for (int i = 0; i <= 1; ++i) { bool strict = (i == 0); - TEST_STATE(strict ? "strict" : "non-strict"); + SCOPED_TRACE(strict ? "strict" : "non-strict"); MatchData::UP md = MatchData::makeTestInstance(100, 10); bp->fetchPostings(strict); SearchIterator::UP search = bp->createSearch(*md, strict); @@ -139,21 +122,21 @@ Test::testPhrase() EXPECT_TRUE(!search->seek(3)); if (strict) { - EXPECT_EQUAL(5u, search->getDocId()); + EXPECT_EQ(5u, search->getDocId()); } else { EXPECT_TRUE(search->seek(5u)); } - EXPECT_EQUAL(5u, search->getDocId()); + EXPECT_EQ(5u, search->getDocId()); { // test doc 5 results search->unpack(5u); { TermFieldMatchData &data = *md->resolveTermField(1); - EXPECT_EQUAL(1u, data.getFieldId()); - EXPECT_EQUAL(5u, data.getDocId()); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_EQ(5u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(1u, itr.size()); + EXPECT_EQ(1u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(3u, itr.getPosition()); + EXPECT_EQ(3u, itr.getPosition()); itr.next(); EXPECT_TRUE(!itr.valid()); } @@ -165,12 +148,7 @@ Test::testPhrase() } } -void -Test::testWeightedSet() -{ - Weight w(100); - - FakeSearchable source; +TEST_F(FakeSearchableTest, require_that_weigheted_set_search_works) { source.addResult("fieldfoo", "friend1", FakeResult().doc(3).doc(5).doc(7).doc(9)); source.addResult("fieldfoo", "friend2", @@ -184,10 +162,10 @@ Test::testWeightedSet() FieldSpecList fields; fields.add(FieldSpec("fieldfoo", 1, 1)); - Blueprint::UP bp = source.createBlueprint(_requestContext, fields, weightedSet); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, weightedSet); for (int i = 0; i <= 1; ++i) { bool strict = (i == 0); - TEST_STATE(strict ? "strict" : "non-strict"); + SCOPED_TRACE(strict ? "strict" : "non-strict"); MatchData::UP md = MatchData::makeTestInstance(100, 10); bp->fetchPostings(strict); SearchIterator::UP search = bp->createSearch(*md, strict); @@ -195,24 +173,24 @@ Test::testWeightedSet() EXPECT_TRUE(!search->seek(2)); if (strict) { - EXPECT_EQUAL(3u, search->getDocId()); + EXPECT_EQ(3u, search->getDocId()); } else { EXPECT_TRUE(search->seek(3u)); } - EXPECT_EQUAL(3u, search->getDocId()); + EXPECT_EQ(3u, search->getDocId()); { // test doc 3 results search->unpack(3u); { TermFieldMatchData &data = *md->resolveTermField(1); - EXPECT_EQUAL(1u, data.getFieldId()); - EXPECT_EQUAL(3u, data.getDocId()); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_EQ(3u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(2u, itr.size()); + EXPECT_EQ(2u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(2, itr.getElementWeight()); + EXPECT_EQ(2, itr.getElementWeight()); itr.next(); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(1, itr.getElementWeight()); + EXPECT_EQ(1, itr.getElementWeight()); itr.next(); EXPECT_TRUE(!itr.valid()); } @@ -227,12 +205,12 @@ Test::testWeightedSet() search->unpack(9u); { TermFieldMatchData &data = *md->resolveTermField(1); - EXPECT_EQUAL(1u, data.getFieldId()); - EXPECT_EQUAL(9u, data.getDocId()); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_EQ(9u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(1u, itr.size()); + EXPECT_EQ(1u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(1, itr.getElementWeight()); + EXPECT_EQ(1, itr.getElementWeight()); itr.next(); EXPECT_TRUE(!itr.valid()); } @@ -244,12 +222,7 @@ Test::testWeightedSet() } } -void -Test::testMultiField() -{ - Weight w(100); - - FakeSearchable source; +TEST_F(FakeSearchableTest, require_that_multi_field_search_works) { source.addResult("fieldfoo", "word1", FakeResult().doc(5).pos(3)); source.addResult("fieldbar", "word1", @@ -260,10 +233,10 @@ Test::testMultiField() FieldSpecList fields; fields.add(FieldSpec("fieldfoo", 1, 1)); fields.add(FieldSpec("fieldbar", 2, 2)); - Blueprint::UP bp = source.createBlueprint(_requestContext, fields, termNode); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, termNode); for (int i = 0; i <= 1; ++i) { bool strict = (i == 0); - TEST_STATE(strict ? "strict" : "non-strict"); + SCOPED_TRACE(strict ? "strict" : "non-strict"); MatchData::UP md = MatchData::makeTestInstance(100, 10); bp->fetchPostings(strict); SearchIterator::UP search = bp->createSearch(*md, strict); @@ -271,58 +244,58 @@ Test::testMultiField() EXPECT_TRUE(!search->seek(3)); if (strict) { - EXPECT_EQUAL(5u, search->getDocId()); + EXPECT_EQ(5u, search->getDocId()); } else { EXPECT_TRUE(search->seek(5u)); } - EXPECT_EQUAL(5u, search->getDocId()); + EXPECT_EQ(5u, search->getDocId()); { // test doc 5 results search->unpack(5u); { TermFieldMatchData &data = *md->resolveTermField(1); - EXPECT_EQUAL(1u, data.getFieldId()); - EXPECT_EQUAL(5u, data.getDocId()); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_EQ(5u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(1u, itr.size()); + EXPECT_EQ(1u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(3u, itr.getPosition()); + EXPECT_EQ(3u, itr.getPosition()); itr.next(); EXPECT_TRUE(!itr.valid()); } { TermFieldMatchData &data = *md->resolveTermField(2); - EXPECT_EQUAL(2u, data.getFieldId()); - EXPECT_EQUAL(5u, data.getDocId()); + EXPECT_EQ(2u, data.getFieldId()); + EXPECT_EQ(5u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(1u, itr.size()); + EXPECT_EQ(1u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(7u, itr.getPosition()); + EXPECT_EQ(7u, itr.getPosition()); itr.next(); EXPECT_TRUE(!itr.valid()); } } EXPECT_TRUE(!search->seek(7)); if (strict) { - EXPECT_EQUAL(10u, search->getDocId()); + EXPECT_EQ(10u, search->getDocId()); } else { EXPECT_TRUE(search->seek(10u)); } - EXPECT_EQUAL(10u, search->getDocId()); + EXPECT_EQ(10u, search->getDocId()); { // test doc 10 results search->unpack(10u); { TermFieldMatchData &data = *md->resolveTermField(1); - EXPECT_EQUAL(1u, data.getFieldId()); - EXPECT_NOT_EQUAL(10u, data.getDocId()); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_NE(10u, data.getDocId()); } { TermFieldMatchData &data = *md->resolveTermField(2); - EXPECT_EQUAL(2u, data.getFieldId()); - EXPECT_EQUAL(10u, data.getDocId()); + EXPECT_EQ(2u, data.getFieldId()); + EXPECT_EQ(10u, data.getDocId()); FieldPositionsIterator itr = data.getIterator(); - EXPECT_EQUAL(1u, itr.size()); + EXPECT_EQ(1u, itr.size()); ASSERT_TRUE(itr.valid()); - EXPECT_EQUAL(2u, itr.getPosition()); + EXPECT_EQ(2u, itr.getPosition()); itr.next(); EXPECT_TRUE(!itr.valid()); } @@ -334,12 +307,7 @@ Test::testMultiField() } } -void -Test::testPhraseWithEmptyChild() -{ - Weight w(100); - - FakeSearchable source; +TEST_F(FakeSearchableTest, require_that_phrase_with_empty_child_works) { source.addResult("fieldfoo", "word1", FakeResult().doc(3).pos(7).doc(5).pos(3)); @@ -349,10 +317,10 @@ Test::testPhraseWithEmptyChild() FieldSpecList fields; fields.add(FieldSpec("fieldfoo", 1, 1)); - Blueprint::UP bp = source.createBlueprint(_requestContext, fields, phraseNode); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, phraseNode); for (int i = 0; i <= 1; ++i) { bool strict = (i == 0); - TEST_STATE(strict ? "strict" : "non-strict"); + SCOPED_TRACE(strict ? "strict" : "non-strict"); MatchData::UP md = MatchData::makeTestInstance(100, 10); bp->fetchPostings(strict); SearchIterator::UP search = bp->createSearch(*md, strict); @@ -365,17 +333,58 @@ Test::testPhraseWithEmptyChild() } } -int -Test::Main() -{ - TEST_INIT("fake_searchable_test"); - testTestFakeResult(); - testTerm(); - testPhrase(); - testWeightedSet(); - testMultiField(); - testPhraseWithEmptyChild(); - TEST_DONE(); +TEST_F(FakeSearchableTest, require_that_match_data_is_compressed_for_attributes) { + source.is_attr(true); + source.addResult("attrfoo", "word1", + FakeResult().doc(5).elem(2).weight(6).pos(3).elem(4).weight(8).pos(5)); + SimpleStringTerm termNode("word1", "viewfoo", 1, w); + FieldSpecList fields; + fields.add(FieldSpec("attrfoo", 1, 1)); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, termNode); + MatchData::UP md = MatchData::makeTestInstance(100, 10); + bp->fetchPostings(false); + SearchIterator::UP search = bp->createSearch(*md, false); + search->initFullRange(); + EXPECT_TRUE(search->seek(5)); + search->unpack(5u); + { + TermFieldMatchData &data = *md->resolveTermField(1); + EXPECT_EQ(1u, data.getFieldId()); + EXPECT_EQ(5u, data.getDocId()); + FieldPositionsIterator itr = data.getIterator(); + EXPECT_EQ(1u, itr.size()); + ASSERT_TRUE(itr.valid()); + EXPECT_EQ(14u, itr.getElementWeight()); // 6 + 8 + itr.next(); + EXPECT_TRUE(!itr.valid()); + } +} + +TEST_F(FakeSearchableTest, require_that_relevant_data_can_be_obtained_from_fake_attribute_search_context) { + source.is_attr(true); + source.addResult("attrfoo", "word1", + FakeResult().doc(5).elem(2).weight(6).pos(3).elem(4).weight(8).pos(5)); + SimpleStringTerm termNode("word1", "viewfoo", 1, w); + FieldSpecList fields; + fields.add(FieldSpec("attrfoo", 1, 1)); + Blueprint::UP bp = source.createBlueprint(req_ctx, fields, termNode); + MatchData::UP md = MatchData::makeTestInstance(100, 10); + bp->fetchPostings(false); + SearchIterator::UP search = bp->createSearch(*md, false); + EXPECT_EQ(bp->get_attribute_search_context(), search->getAttributeSearchContext()); + const auto *attr_ctx = bp->get_attribute_search_context(); + ASSERT_TRUE(attr_ctx); + EXPECT_EQ(attr_ctx->attributeName(), "attrfoo"); + int32_t elem_weight = 0; + EXPECT_EQ(attr_ctx->find(4, 0, elem_weight), -1); + int32_t elem_id = attr_ctx->find(5, 0, elem_weight); + EXPECT_EQ(elem_id, 2); + EXPECT_EQ(elem_weight, 6); + elem_id = attr_ctx->find(5, 3, elem_weight); + EXPECT_EQ(elem_id, 4); + EXPECT_EQ(elem_weight, 8); + EXPECT_EQ(attr_ctx->find(5, 5, elem_weight), -1); + EXPECT_EQ(attr_ctx->find(6, 0, elem_weight), -1); } -TEST_APPHOOK(Test); +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/searchlib/src/vespa/searchlib/queryeval/fake_search.cpp b/searchlib/src/vespa/searchlib/queryeval/fake_search.cpp index 099e28f552a..4ee214b0f16 100644 --- a/searchlib/src/vespa/searchlib/queryeval/fake_search.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/fake_search.cpp @@ -9,32 +9,6 @@ namespace search { namespace queryeval { -namespace { - -struct FakeContext : search::attribute::ISearchContext { - int32_t onFind(DocId, int32_t, int32_t &) const override { return -1; } - int32_t onFind(DocId, int32_t) const override { return -1; } - unsigned int approximateHits() const override { return 0; } - std::unique_ptr<SearchIterator> createIterator(fef::TermFieldMatchData *, bool) override { abort(); } - void fetchPostings(bool) override { } - bool valid() const override { return true; } - search::Int64Range getAsIntegerTerm() const override { abort(); } - const search::QueryTermBase * queryTerm() const override { abort(); } - const vespalib::string &attributeName() const override { abort(); } -}; - -} // namespace search::queryeval::<unnamed> - -void -FakeSearch::is_attr(bool value) -{ - if (value) { - _ctx = std::make_unique<FakeContext>(); - } else { - _ctx.reset(); - } -} - void FakeSearch::doSeek(uint32_t docid) { @@ -59,13 +33,20 @@ FakeSearch::doUnpack(uint32_t docid) const Doc &doc = _result.inspect()[_offset]; assert(doc.docId == docid); _tfmda[0]->reset(docid); + int32_t sum_weight = 0; for (uint32_t i = 0; i < doc.elements.size(); ++i) { - const Elem &elem =doc.elements[i]; - for (uint32_t j = 0; j < elem.positions.size(); ++j) { - _tfmda[0]->appendPosition(PosCtx(elem.id, elem.positions[j], - elem.weight, elem.length)); + const Elem &elem = doc.elements[i]; + sum_weight += elem.weight; + if (!is_attr()) { + for (uint32_t j = 0; j < elem.positions.size(); ++j) { + _tfmda[0]->appendPosition(PosCtx(elem.id, elem.positions[j], + elem.weight, elem.length)); + } } } + if (is_attr()) { + _tfmda[0]->appendPosition(PosCtx(0, 0, sum_weight, 1)); + } } void diff --git a/searchlib/src/vespa/searchlib/queryeval/fake_search.h b/searchlib/src/vespa/searchlib/queryeval/fake_search.h index aa6df480a21..e629e849408 100644 --- a/searchlib/src/vespa/searchlib/queryeval/fake_search.h +++ b/searchlib/src/vespa/searchlib/queryeval/fake_search.h @@ -19,7 +19,7 @@ private: FakeResult _result; uint32_t _offset; fef::TermFieldMatchDataArray _tfmda; - std::unique_ptr<attribute::ISearchContext> _ctx; + const attribute::ISearchContext *_ctx; bool valid() const { return _offset < _result.inspect().size(); } uint32_t currId() const { return _result.inspect()[_offset].docId; } @@ -32,16 +32,18 @@ public: const FakeResult &res, const fef::TermFieldMatchDataArray &tfmda) : _tag(tag), _field(field), _term(term), - _result(res), _offset(0), _tfmda(tfmda) + _result(res), _offset(0), _tfmda(tfmda), + _ctx(nullptr) { assert(_tfmda.size() == 1); } - void is_attr(bool value); + void attr_ctx(const attribute::ISearchContext *ctx) { _ctx = ctx; } + bool is_attr() const { return (_ctx != nullptr); } void doSeek(uint32_t docid) override; void doUnpack(uint32_t docid) override; const PostingInfo *getPostingInfo() const override { return _result.postingInfo(); } void visitMembers(vespalib::ObjectVisitor &visitor) const override; - const attribute::ISearchContext *getAttributeSearchContext() const override { return _ctx.get(); } + const attribute::ISearchContext *getAttributeSearchContext() const override { return _ctx; } }; } // namespace queryeval diff --git a/searchlib/src/vespa/searchlib/queryeval/fake_searchable.cpp b/searchlib/src/vespa/searchlib/queryeval/fake_searchable.cpp index 997f9afc54f..4c678a9902f 100644 --- a/searchlib/src/vespa/searchlib/queryeval/fake_searchable.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/fake_searchable.cpp @@ -21,7 +21,8 @@ namespace search::queryeval { FakeSearchable::FakeSearchable() : _tag("<undef>"), - _map() + _map(), + _is_attr(false) { } @@ -44,10 +45,11 @@ class LookupVisitor : public CreateBlueprintVisitorHelper { const Map &_map; const vespalib::string _tag; + bool _is_attr; public: LookupVisitor(Searchable &searchable, const IRequestContext & requestContext, - const Map &map, const vespalib::string &tag, const FieldSpec &field); + const Map &map, const vespalib::string &tag, bool is_attr, const FieldSpec &field); ~LookupVisitor(); template <class TermNode> @@ -66,10 +68,11 @@ public: template <class Map> LookupVisitor<Map>::LookupVisitor(Searchable &searchable, const IRequestContext & requestContext, - const Map &map, const vespalib::string &tag, const FieldSpec &field) + const Map &map, const vespalib::string &tag, bool is_attr, const FieldSpec &field) : CreateBlueprintVisitorHelper(searchable, field, requestContext), _map(map), - _tag(tag) + _tag(tag), + _is_attr(is_attr) {} template <class Map> @@ -86,8 +89,8 @@ LookupVisitor<Map>::visitTerm(TermNode &n) { if (pos != _map.end()) { result = pos->second; } - auto fake = std::make_unique<FakeBlueprint>(getField(), result); - fake->tag(_tag).term(term_string); + auto fake = std::make_unique<FakeBlueprint>(getField(), result); + fake->tag(_tag).is_attr(_is_attr).term(term_string); setResult(std::move(fake)); } @@ -98,7 +101,7 @@ FakeSearchable::createBlueprint(const IRequestContext & requestContext, const FieldSpec &field, const search::query::Node &term) { - LookupVisitor<Map> visitor(*this, requestContext, _map, _tag, field); + LookupVisitor<Map> visitor(*this, requestContext, _map, _tag, _is_attr, field); const_cast<Node &>(term).accept(visitor); return visitor.getResult(); } diff --git a/searchlib/src/vespa/searchlib/queryeval/fake_searchable.h b/searchlib/src/vespa/searchlib/queryeval/fake_searchable.h index 8f95c116bf5..a47ac18d88b 100644 --- a/searchlib/src/vespa/searchlib/queryeval/fake_searchable.h +++ b/searchlib/src/vespa/searchlib/queryeval/fake_searchable.h @@ -17,11 +17,11 @@ class FakeSearchable : public Searchable { private: typedef std::pair<vespalib::string, vespalib::string> Key; - typedef FakeResult Value; - typedef std::map<Key, Value> Map; + typedef std::map<Key, FakeResult> Map; vespalib::string _tag; - Map _map; + Map _map; + bool _is_attr; public: /** @@ -42,6 +42,16 @@ public: } /** + * Is this searchable searching attributes? Setting this to true + * will result in blueprints and search iterators exposing a + * mocked attribute search context interface. + **/ + FakeSearchable &is_attr(bool value) { + _is_attr = value; + return *this; + } + + /** * Add a fake result to be returned for lookup on the given field * and term combination. * diff --git a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp index a140fb146d5..042de113a35 100644 --- a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp @@ -61,11 +61,46 @@ SimpleBlueprint::tag(const vespalib::string &t) //----------------------------------------------------------------------------- +namespace { + +struct FakeContext : attribute::ISearchContext { + const vespalib::string &name; + const FakeResult &result; + FakeContext(const vespalib::string &name_in, const FakeResult &result_in) + : name(name_in), result(result_in) {} + int32_t onFind(DocId docid, int32_t elemid, int32_t &weight) const override { + for (const auto &doc: result.inspect()) { + if (doc.docId == docid) { + for (const auto &elem: doc.elements) { + if (elem.id >= uint32_t(elemid)) { + weight = elem.weight; + return elem.id; + } + } + } + } + return -1; + } + int32_t onFind(DocId docid, int32_t elem) const override { + int32_t ignore_weight; + return onFind(docid, elem, ignore_weight); + } + unsigned int approximateHits() const override { return 0; } + std::unique_ptr<SearchIterator> createIterator(fef::TermFieldMatchData *, bool) override { abort(); } + void fetchPostings(bool) override { } + bool valid() const override { return true; } + search::Int64Range getAsIntegerTerm() const override { abort(); } + const search::QueryTermBase * queryTerm() const override { abort(); } + const vespalib::string &attributeName() const override { return name; } +}; + +} + SearchIterator::UP FakeBlueprint::createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool) const { auto result = std::make_unique<FakeSearch>(_tag, _field.getName(), _term, _result, tfmda); - result->is_attr(_is_attr); + result->attr_ctx(_ctx.get()); return result; } @@ -75,11 +110,21 @@ FakeBlueprint::FakeBlueprint(const FieldSpec &field, const FakeResult &result) _term("<term>"), _field(field), _result(result), - _is_attr(false) + _ctx() { setEstimate(HitEstimate(result.inspect().size(), result.inspect().empty())); } FakeBlueprint::~FakeBlueprint() = default; +FakeBlueprint & +FakeBlueprint::is_attr(bool value) { + if (value) { + _ctx = std::make_unique<FakeContext>(_field.getName(), _result); + } else { + _ctx.reset(); + } + return *this; +} + } diff --git a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h index 85d30aaf003..2dc2d938bb6 100644 --- a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h +++ b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h @@ -51,7 +51,7 @@ private: vespalib::string _term; FieldSpec _field; FakeResult _result; - bool _is_attr; + std::unique_ptr<attribute::ISearchContext> _ctx; protected: SearchIterator::UP @@ -67,16 +67,17 @@ public: } const vespalib::string &tag() const { return _tag; } - FakeBlueprint &is_attr(bool value) { - _is_attr = value; - return *this; - } - bool is_attr() const { return _is_attr; } + FakeBlueprint &is_attr(bool value); + bool is_attr() const { return bool(_ctx); } FakeBlueprint &term(const vespalib::string &t) { _term = t; return *this; } + + const attribute::ISearchContext *get_attribute_search_context() const override { + return _ctx.get(); + } }; //----------------------------------------------------------------------------- |