diff options
Diffstat (limited to 'searchlib/src/tests/queryeval/predicate/predicate_blueprint_test.cpp')
-rw-r--r-- | searchlib/src/tests/queryeval/predicate/predicate_blueprint_test.cpp | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/searchlib/src/tests/queryeval/predicate/predicate_blueprint_test.cpp b/searchlib/src/tests/queryeval/predicate/predicate_blueprint_test.cpp new file mode 100644 index 00000000000..3b609849141 --- /dev/null +++ b/searchlib/src/tests/queryeval/predicate/predicate_blueprint_test.cpp @@ -0,0 +1,241 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Unit tests for predicate_blueprint. + +#include <vespa/log/log.h> +LOG_SETUP("predicate_blueprint_test"); +#include <vespa/fastos/fastos.h> + +#include <vespa/searchlib/attribute/attributeguard.h> +#include <vespa/searchlib/attribute/predicate_attribute.h> +#include <vespa/searchlib/predicate/predicate_tree_annotator.h> +#include <vespa/searchlib/fef/termfieldmatchdataarray.h> +#include <vespa/searchlib/query/tree/predicate_query_term.h> +#include <vespa/searchlib/query/tree/simplequery.h> +#include <vespa/searchlib/query/weight.h> +#include <vespa/searchlib/queryeval/field_spec.h> +#include <vespa/searchlib/queryeval/predicate_blueprint.h> +#include <vespa/searchlib/predicate/predicate_hash.h> +#include <vespa/vespalib/testkit/testapp.h> + +using namespace search; +using namespace search::predicate; +using search::fef::TermFieldMatchDataArray; +using search::query::PredicateQueryTerm; +using search::query::SimplePredicateQuery; +using search::query::Weight; +using search::queryeval::FieldSpecBase; +using search::queryeval::PredicateBlueprint; +using search::queryeval::SearchIterator; + +namespace { + +struct Fixture { + FieldSpecBase field; + AttributeVector::SP attribute; + vespalib::GenerationHandler generation_handler; + SimplePredicateQuery query; + + using IntervalRange = PredicateAttribute::IntervalRange; + + Fixture() + : field(42, 0), + attribute(new PredicateAttribute("f", attribute::Config(attribute::BasicType::PREDICATE))), + query(PredicateQueryTerm::UP(new PredicateQueryTerm), + "view", 0, Weight(1)) { + query.getTerm()->addFeature("key", "value"); + query.getTerm()->addRangeFeature("range_key", 42); + } + PredicateAttribute & guard() { + return dynamic_cast<PredicateAttribute &>(*attribute); + } + PredicateIndex & index() { + return predicate().getIndex(); + } + PredicateAttribute & predicate() { return static_cast<PredicateAttribute &>(*attribute); } + void resize(uint32_t doc_id) { + while (predicate().getNumDocs() <= doc_id) { + uint32_t tmp; + predicate().addDoc(tmp); + PredicateAttribute::MinFeatureHandle mfh = predicate().getMinFeatureVector(); + const_cast<uint8_t *>(mfh.first)[tmp] = 0; + } + } + void setIntervalRange(uint32_t doc_id, IntervalRange interval_range) { + const_cast<IntervalRange *>(predicate().getIntervalRangeVector())[doc_id] = interval_range; + } + void indexEmptyDocument(uint32_t doc_id, IntervalRange ir = 0x1) { + resize(doc_id); + index().indexEmptyDocument(doc_id); + setIntervalRange(doc_id, ir); + predicate().updateMaxIntervalRange(ir); + predicate().commit(false); + } + void indexDocument(uint32_t doc_id, const PredicateTreeAnnotations &annotations, IntervalRange ir = 0xffff) { + resize(doc_id); + index().indexDocument(doc_id, annotations); + setIntervalRange(doc_id, ir); + predicate().updateMaxIntervalRange(ir); + predicate().commit(false); + } +}; + +TEST_F("require that blueprint with empty index estimates empty.", Fixture) { + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + EXPECT_TRUE(blueprint.getState().estimate().empty); + EXPECT_EQUAL(0u, blueprint.getState().estimate().estHits); +} + +TEST_F("require that blueprint with zero-constraint doc estimates non-empty.", + Fixture) { + f.indexEmptyDocument(42); + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + EXPECT_FALSE(blueprint.getState().estimate().empty); + EXPECT_EQUAL(1u, blueprint.getState().estimate().estHits); +} + +const int min_feature = 1; +const uint32_t doc_id = 2; +const uint32_t interval = 0x0001ffff; + +TEST_F("require that blueprint with posting list entry estimates non-empty.", + Fixture) { + PredicateTreeAnnotations annotations(min_feature); + annotations.interval_map[PredicateHash::hash64("key=value")] = + std::vector<Interval>{{interval}}; + f.indexDocument(doc_id, annotations); + + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + EXPECT_FALSE(blueprint.getState().estimate().empty); + EXPECT_EQUAL(0u, blueprint.getState().estimate().estHits); +} + +TEST_F("require that blueprint with 'bounds' posting list entry estimates " + "non-empty.", Fixture) { + PredicateTreeAnnotations annotations(min_feature); + annotations.bounds_map[PredicateHash::hash64("range_key=40")] = + std::vector<IntervalWithBounds>{{interval, 0x80000003}}; + f.indexDocument(doc_id, annotations); + + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + EXPECT_FALSE(blueprint.getState().estimate().empty); + EXPECT_EQUAL(0u, blueprint.getState().estimate().estHits); +} + +TEST_F("require that blueprint with zstar-compressed estimates non-empty.", + Fixture) { + PredicateTreeAnnotations annotations(1); + annotations.interval_map[PredicateIndex::z_star_compressed_hash] = + std::vector<Interval>{{0xfffe0000}}; + f.indexDocument(doc_id, annotations); + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + EXPECT_FALSE(blueprint.getState().estimate().empty); + EXPECT_EQUAL(0u, blueprint.getState().estimate().estHits); +} + +TEST_F("require that blueprint can create search", Fixture) { + PredicateTreeAnnotations annotations(1); + annotations.interval_map[PredicateHash::hash64("key=value")] = + std::vector<Interval>{{interval}}; + f.indexDocument(doc_id, annotations); + + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + blueprint.fetchPostings(true); + TermFieldMatchDataArray tfmda; + SearchIterator::UP it = blueprint.createLeafSearch(tfmda, true); + ASSERT_TRUE(it.get()); + it->initFullRange(); + EXPECT_EQUAL(SearchIterator::beginId(), it->getDocId()); + EXPECT_FALSE(it->seek(doc_id - 1)); + EXPECT_EQUAL(doc_id, it->getDocId()); + EXPECT_TRUE(it->seek(doc_id)); + EXPECT_EQUAL(doc_id, it->getDocId()); + EXPECT_FALSE(it->seek(doc_id + 1)); + EXPECT_TRUE(it->isAtEnd()); +} + +TEST_F("require that blueprint can create more advanced search", Fixture) { + PredicateTreeAnnotations annotations(2); + annotations.interval_map[PredicateHash::hash64("key=value")] = + std::vector<Interval>{{0x00010001}}; + annotations.bounds_map[PredicateHash::hash64("range_key=40")] = + std::vector<IntervalWithBounds>{{0x00020010, 0x40000005}}; // [40..44] + f.indexDocument(doc_id, annotations, 0x10); + f.indexEmptyDocument(doc_id + 2); + + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + blueprint.fetchPostings(true); + TermFieldMatchDataArray tfmda; + SearchIterator::UP it = blueprint.createLeafSearch(tfmda, true); + ASSERT_TRUE(it.get()); + it->initFullRange(); + EXPECT_EQUAL(SearchIterator::beginId(), it->getDocId()); + EXPECT_FALSE(it->seek(doc_id - 1)); + EXPECT_EQUAL(doc_id, it->getDocId()); + EXPECT_TRUE(it->seek(doc_id)); + EXPECT_EQUAL(doc_id, it->getDocId()); + EXPECT_FALSE(it->seek(doc_id + 1)); + EXPECT_EQUAL(doc_id + 2, it->getDocId()); + EXPECT_TRUE(it->seek(doc_id + 2)); + EXPECT_FALSE(it->seek(doc_id + 3)); + EXPECT_TRUE(it->isAtEnd()); +} + +TEST_F("require that blueprint can create NOT search", Fixture) { + PredicateTreeAnnotations annotations(1); + annotations.interval_map[PredicateIndex::z_star_hash] = + std::vector<Interval>{{0x00010000}, {0xffff0001}}; + f.indexDocument(doc_id, annotations); + + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + blueprint.fetchPostings(true); + TermFieldMatchDataArray tfmda; + SearchIterator::UP it = blueprint.createLeafSearch(tfmda, true); + ASSERT_TRUE(it.get()); + it->initFullRange(); + EXPECT_TRUE(it->seek(doc_id)); + EXPECT_EQUAL(doc_id, it->getDocId()); + EXPECT_FALSE(it->seek(doc_id + 1)); +} + +TEST_F("require that blueprint can create compressed NOT search", Fixture) { + PredicateTreeAnnotations annotations(1); + annotations.interval_map[PredicateIndex::z_star_compressed_hash] = + std::vector<Interval>{{0xfffe0000}}; + f.indexDocument(doc_id, annotations); + + PredicateBlueprint blueprint(f.field, f.guard(), f.query); + blueprint.fetchPostings(true); + TermFieldMatchDataArray tfmda; + SearchIterator::UP it = blueprint.createLeafSearch(tfmda, true); + ASSERT_TRUE(it.get()); + it->initFullRange(); + EXPECT_TRUE(it->seek(doc_id)); + EXPECT_EQUAL(doc_id, it->getDocId()); + EXPECT_FALSE(it->seek(doc_id + 1)); +} + +TEST_F("require that blueprint can set up search with subqueries", Fixture) { + PredicateTreeAnnotations annotations(2); + annotations.interval_map[PredicateHash::hash64("key=value")] = + std::vector<Interval>{{0x00010001}}; + annotations.interval_map[PredicateHash::hash64("key2=value")] = + std::vector<Interval>{{0x0002ffff}}; + f.indexDocument(doc_id, annotations); + + SimplePredicateQuery query(PredicateQueryTerm::UP(new PredicateQueryTerm), + "view", 0, Weight(1)); + query.getTerm()->addFeature("key", "value", 1); + query.getTerm()->addFeature("key2", "value", 2); + + PredicateBlueprint blueprint(f.field, f.guard(), query); + blueprint.fetchPostings(true); + TermFieldMatchDataArray tfmda; + SearchIterator::UP it = blueprint.createLeafSearch(tfmda, true); + ASSERT_TRUE(it.get()); + it->initFullRange(); + EXPECT_FALSE(it->seek(doc_id)); +} + +} // namespace + +TEST_MAIN() { TEST_RUN_ALL(); } |