// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. // Unit tests for querynodes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_SETUP("querynodes_test"); using search::fef::FieldInfo; using search::fef::FieldType; using search::fef::MatchData; using search::fef::MatchDataLayout; using search::fef::TermFieldHandle; using search::fef::TermFieldMatchData; using search::fef::TermFieldMatchDataArray; using search::fef::test::IndexEnvironment; using search::query::Node; using search::query::QueryBuilder; using search::queryeval::AndNotSearch; using search::queryeval::AndSearch; using search::queryeval::Blueprint; using search::queryeval::ChildrenIterators; using search::queryeval::ElementIteratorWrapper; using search::queryeval::ElementIterator; using search::queryeval::EmptySearch; using search::queryeval::FakeRequestContext; using search::queryeval::FakeResult; using search::queryeval::FakeSearch; using search::queryeval::FieldSpec; using search::queryeval::ISourceSelector; using search::queryeval::NearSearch; using search::queryeval::ONearSearch; using search::queryeval::OrSearch; using search::queryeval::RankSearch; using search::queryeval::SameElementSearch; using search::queryeval::SearchIterator; using search::queryeval::Searchable; using search::queryeval::SimplePhraseSearch; using search::queryeval::SourceBlenderSearch; using std::string; using std::vector; using namespace proton::matching; namespace fef_test = search::fef::test; using CollectionType = FieldInfo::CollectionType; namespace { template void checkTwoFieldsTwoAttributesTwoIndexes(); template void checkTwoFieldsTwoAttributesOneIndex(); template void checkOneFieldOneAttributeTwoIndexes(); template void checkOneFieldNoAttributesTwoIndexes(); template void checkTwoFieldsNoAttributesTwoIndexes(); template void checkOneFieldNoAttributesOneIndex(); template void checkProperBlending(); template void checkProperBlendingWithParent(); const string term = "term"; const string phrase_term1 = "hello"; const string phrase_term2 = "world"; const string view = "view"; const uint32_t id = 3; const search::query::Weight weight(7); const string field[] = { "field1", "field2" }; const string attribute[] = { "attribute1", "attribute2" }; const string source_tag[] = { "Source 1", "Source 2" }; const string attribute_tag = "Attribute source"; const uint32_t distance = 13; template class Create { bool _strict; typename SearchType::Children _children; public: explicit Create(bool strict = true) : _strict(strict) {} Create &add(SearchIterator *s) { _children.emplace_back(s); return *this; } operator SearchIterator *() { return SearchType::create(std::move(_children), _strict).release(); } }; using MyOr = Create; class ISourceSelectorDummy : public ISourceSelector { public: static SourceStore _sourceStoreDummy; using Iterator = search::queryeval::sourceselector::Iterator; static std::unique_ptr makeDummyIterator() { return std::make_unique(_sourceStoreDummy); } }; ISourceSelector::SourceStore ISourceSelectorDummy::_sourceStoreDummy("foo"); using SourceId = uint32_t; class Blender { bool _strict; SourceBlenderSearch::Children _children; public: explicit Blender(bool strict = true) : _strict(strict) {} Blender &add(SourceId source_id, SearchIterator *search) { _children.push_back(SourceBlenderSearch::Child(search, source_id)); return *this; } operator SearchIterator *() { return SourceBlenderSearch::create( ISourceSelectorDummy::makeDummyIterator(), _children, _strict).release(); } }; SearchIterator *getTerm(const string &trm, const string &fld, const string &tag) { static TermFieldMatchData tmd; TermFieldMatchDataArray tfmda; tfmda.add(&tmd); return new FakeSearch(tag, fld, trm, FakeResult(), tfmda); } class IteratorStructureTest { int _field_count; int _attribute_count; int _index_count; public: void setFieldCount(int count) { _field_count = count; } void setAttributeCount(int count) { _attribute_count = count; } void setIndexCount(int count) { _index_count = count; } string getIteratorAsString(Node &node) { ViewResolver resolver; for (int i = 0; i < _field_count; ++i) { resolver.add(view, field[i]); } for (int i = 0; i < _attribute_count; ++i) { resolver.add(view, attribute[i]); } fef_test::IndexEnvironment index_environment; uint32_t fieldId = 0; for (int i = 0; i < _field_count; ++i) { FieldInfo field_info(FieldType::INDEX, CollectionType::SINGLE, field[i], fieldId++); index_environment.getFields().push_back(field_info); } for (int i = 0; i < _attribute_count; ++i) { FieldInfo field_info(FieldType::ATTRIBUTE, CollectionType::SINGLE, attribute[i], fieldId++); index_environment.getFields().push_back(field_info); } ResolveViewVisitor resolve_visitor(resolver, index_environment); node.accept(resolve_visitor); FakeSearchContext context; context.attr().tag(attribute_tag); for (int i = 0; i < _index_count; ++i) { context.addIdx(i).idx(i).getFake().tag(source_tag[i]); } MatchDataLayout mdl; FakeRequestContext requestContext; MatchDataReserveVisitor reserve_visitor(mdl); node.accept(reserve_visitor); MatchData::UP match_data = mdl.createMatchData(); Blueprint::UP blueprint = BlueprintBuilder::build(requestContext, node, context); blueprint->fetchPostings(search::queryeval::ExecuteInfo::TRUE); return blueprint->createSearch(*match_data, true)->asString(); } template string getIteratorAsString(); }; using QB = QueryBuilder; struct Phrase { void addToBuilder(QB& b) { b.addPhrase(2, view, id, weight); }}; struct SameElement { void addToBuilder(QB& b) { b.addSameElement(2, view, id, weight); }}; struct Near { void addToBuilder(QB& b) { b.addNear(2, distance); } }; struct ONear { void addToBuilder(QB& b) { b.addONear(2, distance); } }; struct Or { void addToBuilder(QB& b) { b.addOr(2); } }; struct And { void addToBuilder(QB& b) { b.addAnd(2); } }; struct AndNot { void addToBuilder(QB& b) { b.addAndNot(2); } }; struct Rank { void addToBuilder(QB& b) { b.addRank(2); } }; struct Term {}; template string IteratorStructureTest::getIteratorAsString() { QueryBuilder query_builder; Tag().addToBuilder(query_builder); query_builder.addStringTerm(phrase_term1, view, id, weight); query_builder.addStringTerm(phrase_term2, view, id, weight); Node::UP node = query_builder.build(); return getIteratorAsString(*node); } template <> string IteratorStructureTest::getIteratorAsString() { ProtonStringTerm node(term, view, id, weight); return getIteratorAsString(node); } template SearchIterator *getLeaf(const string &fld, const string &tag) { return getTerm(term, fld, tag); } template <> SearchIterator *getLeaf(const string &fld, const string &tag) { SimplePhraseSearch::Children children; children.emplace_back(getTerm(phrase_term1, fld, tag)); children.emplace_back(getTerm(phrase_term2, fld, tag)); static TermFieldMatchData tmd; TermFieldMatchDataArray tfmda; tfmda.add(&tmd).add(&tmd); vector eval_order(2); return new SimplePhraseSearch(std::move(children), MatchData::UP(), tfmda, eval_order, tmd, true); } template SearchIterator *getNearParent(SearchIterator *a, SearchIterator *b) { typename NearType::Children children; children.emplace_back(a); children.emplace_back(b); TermFieldMatchDataArray data; static TermFieldMatchData tmd; // we only check how many term/field combinations // are below the NearType parent: // two terms searching in (two index fields + two attribute fields) data.add(&tmd).add(&tmd).add(&tmd).add(&tmd) .add(&tmd).add(&tmd).add(&tmd).add(&tmd); return new NearType(std::move(children), data, distance, true); } template SearchIterator *getSimpleParent(SearchIterator *a, SearchIterator *b) { typename SearchType::Children children; children.emplace_back(a); children.emplace_back(b); return SearchType::create(std::move(children), true).release(); } template SearchIterator *getParent(SearchIterator *a, SearchIterator *b); template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { return getNearParent(a, b); } template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { return getNearParent(a, b); } template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { static TermFieldMatchData tmd; std::vector children; children.emplace_back(std::make_unique(SearchIterator::UP(a), tmd)); children.emplace_back(std::make_unique(SearchIterator::UP(b), tmd)); // we only check how many term/field combinations // are below the SameElement parent: // two terms searching in one index field return new SameElementSearch(tmd, nullptr, std::move(children), true); } template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { return getSimpleParent(a, b); } template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { return getSimpleParent(a, b); } template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { return getSimpleParent(a, b); } template <> SearchIterator *getParent(SearchIterator *a, SearchIterator *b) { return getSimpleParent(a, b); } template bool bothStrict() { return false; } template <> bool bothStrict() { return true; } template void checkTwoFieldsTwoAttributesTwoIndexes() { IteratorStructureTest structure_test; structure_test.setFieldCount(2); structure_test.setAttributeCount(2); structure_test.setIndexCount(2); SearchIterator::UP expected( MyOr() .add(getLeaf(attribute[0], attribute_tag)) .add(getLeaf(attribute[1], attribute_tag)) .add(Blender() .add(SourceId(0), MyOr() .add(getLeaf(field[0], source_tag[0])) .add(getLeaf(field[1], source_tag[0]))) .add(SourceId(1), MyOr() .add(getLeaf(field[0], source_tag[1])) .add(getLeaf(field[1], source_tag[1]))))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template void checkTwoFieldsTwoAttributesOneIndex() { IteratorStructureTest structure_test; structure_test.setFieldCount(2); structure_test.setAttributeCount(2); structure_test.setIndexCount(1); SearchIterator::UP expected( MyOr() .add(getLeaf(attribute[0], attribute_tag)) .add(getLeaf(attribute[1], attribute_tag)) .add(Blender() .add(SourceId(0), MyOr() .add(getLeaf(field[0], source_tag[0])) .add(getLeaf(field[1], source_tag[0]))))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template void checkOneFieldOneAttributeTwoIndexes() { IteratorStructureTest structure_test; structure_test.setFieldCount(1); structure_test.setAttributeCount(1); structure_test.setIndexCount(2); SearchIterator::UP expected( MyOr() .add(getLeaf(attribute[0], attribute_tag)) .add(Blender() .add(SourceId(0), getLeaf(field[0], source_tag[0])) .add(SourceId(1), getLeaf(field[0], source_tag[1])))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template void checkOneFieldNoAttributesTwoIndexes() { IteratorStructureTest structure_test; structure_test.setFieldCount(1); structure_test.setAttributeCount(0); structure_test.setIndexCount(2); SearchIterator::UP expected( Blender() .add(SourceId(0), getLeaf(field[0], source_tag[0])) .add(SourceId(1), getLeaf(field[0], source_tag[1]))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template void checkTwoFieldsNoAttributesTwoIndexes() { IteratorStructureTest structure_test; structure_test.setFieldCount(2); structure_test.setAttributeCount(0); structure_test.setIndexCount(2); SearchIterator::UP expected( Blender() .add(SourceId(0), MyOr() .add(getLeaf(field[0], source_tag[0])) .add(getLeaf(field[1], source_tag[0]))) .add(SourceId(1), MyOr() .add(getLeaf(field[0], source_tag[1])) .add(getLeaf(field[1], source_tag[1])))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template void checkOneFieldNoAttributesOneIndex() { IteratorStructureTest structure_test; structure_test.setFieldCount(1); structure_test.setAttributeCount(0); structure_test.setIndexCount(1); SearchIterator::UP expected( Blender() .add(SourceId(0), getLeaf(field[0], source_tag[0]))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template void checkProperBlending() { TEST_DO(checkTwoFieldsTwoAttributesTwoIndexes()); TEST_DO(checkTwoFieldsTwoAttributesOneIndex()); TEST_DO(checkOneFieldOneAttributeTwoIndexes()); TEST_DO(checkOneFieldNoAttributesTwoIndexes()); TEST_DO(checkTwoFieldsNoAttributesTwoIndexes()); TEST_DO(checkOneFieldNoAttributesOneIndex()); } template void checkProperBlendingWithParent() { IteratorStructureTest structure_test; structure_test.setFieldCount(2); structure_test.setAttributeCount(2); structure_test.setIndexCount(2); SearchIterator::UP expected( getParent( MyOr() .add(getTerm(phrase_term1, attribute[0], attribute_tag)) .add(getTerm(phrase_term1, attribute[1], attribute_tag)) .add(Blender() .add(SourceId(0), MyOr() .add(getTerm(phrase_term1, field[0], source_tag[0])) .add(getTerm(phrase_term1, field[1], source_tag[0]))) .add(SourceId(1), MyOr() .add(getTerm(phrase_term1, field[0], source_tag[1])) .add(getTerm(phrase_term1, field[1], source_tag[1])))), MyOr(bothStrict()) .add(getTerm(phrase_term2, attribute[0], attribute_tag)) .add(getTerm(phrase_term2, attribute[1], attribute_tag)) .add(Blender(bothStrict()) .add(SourceId(0), MyOr(bothStrict()) .add(getTerm(phrase_term2, field[0], source_tag[0])) .add(getTerm(phrase_term2, field[1], source_tag[0]))) .add(SourceId(1), MyOr(bothStrict()) .add(getTerm(phrase_term2, field[0], source_tag[1])) .add(getTerm(phrase_term2, field[1], source_tag[1])))))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } template <> void checkProperBlendingWithParent() { using T = SameElement; IteratorStructureTest structure_test; structure_test.setFieldCount(1); structure_test.setAttributeCount(0); structure_test.setIndexCount(2); SearchIterator::UP expected( getParent(Blender() .add(SourceId(0), getTerm(phrase_term1, field[0], source_tag[0])) .add(SourceId(1), getTerm(phrase_term1, field[0], source_tag[1])), Blender(bothStrict()) .add(SourceId(0), getTerm(phrase_term2, field[0], source_tag[0])) .add(SourceId(1), getTerm(phrase_term2, field[0], source_tag[1])))); EXPECT_EQUAL(expected->asString(), structure_test.getIteratorAsString()); } TEST("requireThatTermNodeSearchIteratorsGetProperBlending") { TEST_DO(checkProperBlending()); } TEST("requireThatPhrasesGetProperBlending") { TEST_DO(checkProperBlending()); } TEST("requireThatSameElementGetProperBlending") { TEST_DO(checkProperBlendingWithParent()); } TEST("requireThatNearGetProperBlending") { TEST_DO(checkProperBlendingWithParent()); } TEST("requireThatONearGetProperBlending") { TEST_DO(checkProperBlendingWithParent()); } TEST("requireThatSimpleIntermediatesGetProperBlending") { TEST_DO(checkProperBlendingWithParent()); TEST_DO(checkProperBlendingWithParent()); TEST_DO(checkProperBlendingWithParent()); TEST_DO(checkProperBlendingWithParent()); } TEST("control query nodes size") { EXPECT_EQUAL(128u, sizeof(ProtonTermData)); EXPECT_EQUAL(160u, sizeof(search::query::NumberTerm)); EXPECT_EQUAL(288u, sizeof(ProtonNodeTypes::NumberTerm)); EXPECT_EQUAL(160u, sizeof(search::query::StringTerm)); EXPECT_EQUAL(288u, sizeof(ProtonNodeTypes::StringTerm)); } } // namespace TEST_MAIN() { TEST_RUN_ALL(); }