blob: 629fb3447137ba666ace2ee749c45546222ba32f [file] [log] [blame]
// Copyright (C) 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <vector>
#include "gtest/gtest.h"
#include "icing/document-builder.h"
#include "icing/portable/equals-proto.h"
#include "icing/portable/platform.h"
#include "icing/proto/document.pb.h"
#include "icing/proto/schema.pb.h"
#include "icing/proto/search.pb.h"
#include "icing/proto/term.pb.h"
#include "icing/result/page-result.h"
#include "icing/result/projection-tree.h"
#include "icing/result/result-adjustment-info.h"
#include "icing/result/result-retriever-v2.h"
#include "icing/result/result-state-v2.h"
#include "icing/schema-builder.h"
#include "icing/schema/schema-store.h"
#include "icing/schema/section.h"
#include "icing/scoring/priority-queue-scored-document-hits-ranker.h"
#include "icing/scoring/scored-document-hit.h"
#include "icing/store/document-id.h"
#include "icing/testing/common-matchers.h"
#include "icing/testing/fake-clock.h"
#include "icing/testing/icu-data-file-helper.h"
#include "icing/testing/test-data.h"
#include "icing/testing/tmp-directory.h"
#include "icing/tokenization/language-segmenter-factory.h"
#include "icing/transform/normalizer-factory.h"
#include "icing/transform/normalizer.h"
#include "unicode/uloc.h"
namespace icing {
namespace lib {
namespace {
using ::icing::lib::portable_equals_proto::EqualsProto;
using ::testing::SizeIs;
class ResultRetrieverV2ProjectionTest : public testing::Test {
protected:
ResultRetrieverV2ProjectionTest() : test_dir_(GetTestTempDir() + "/icing") {
filesystem_.CreateDirectoryRecursively(test_dir_.c_str());
}
void SetUp() override {
if (!IsCfStringTokenization() && !IsReverseJniTokenization()) {
ICING_ASSERT_OK(
// File generated via icu_data_file rule in //icing/BUILD.
icu_data_file_helper::SetUpICUDataFile(
GetTestFilePath("icing/icu.dat")));
}
language_segmenter_factory::SegmenterOptions options(ULOC_US);
ICING_ASSERT_OK_AND_ASSIGN(
language_segmenter_,
language_segmenter_factory::Create(std::move(options)));
ICING_ASSERT_OK_AND_ASSIGN(
schema_store_,
SchemaStore::Create(&filesystem_, test_dir_, &fake_clock_));
ICING_ASSERT_OK_AND_ASSIGN(normalizer_, normalizer_factory::Create(
/*max_term_byte_size=*/10000));
SchemaProto schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder()
.SetType("Email")
.AddProperty(PropertyConfigBuilder()
.SetName("name")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_EXACT,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(
PropertyConfigBuilder()
.SetName("sender")
.SetDataTypeDocument(
"Person", /*index_nested_properties=*/true)
.SetCardinality(CARDINALITY_OPTIONAL)))
.AddType(
SchemaTypeConfigBuilder()
.SetType("Person")
.AddProperty(PropertyConfigBuilder()
.SetName("name")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("emailAddress")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.Build();
ASSERT_THAT(schema_store_->SetSchema(schema), IsOk());
ICING_ASSERT_OK_AND_ASSIGN(
DocumentStore::CreateResult create_result,
DocumentStore::Create(&filesystem_, test_dir_, &fake_clock_,
schema_store_.get()));
document_store_ = std::move(create_result.document_store);
}
void TearDown() override {
filesystem_.DeleteDirectoryRecursively(test_dir_.c_str());
}
SectionId GetSectionId(const std::string& type, const std::string& property) {
auto type_id_or = schema_store_->GetSchemaTypeId(type);
if (!type_id_or.ok()) {
return kInvalidSectionId;
}
SchemaTypeId type_id = type_id_or.ValueOrDie();
for (SectionId section_id = 0; section_id <= kMaxSectionId; ++section_id) {
auto metadata_or = schema_store_->GetSectionMetadata(type_id, section_id);
if (!metadata_or.ok()) {
break;
}
const SectionMetadata* metadata = metadata_or.ValueOrDie();
if (metadata->path == property) {
return metadata->id;
}
}
return kInvalidSectionId;
}
const Filesystem filesystem_;
const std::string test_dir_;
std::unique_ptr<LanguageSegmenter> language_segmenter_;
std::unique_ptr<SchemaStore> schema_store_;
std::unique_ptr<Normalizer> normalizer_;
std::unique_ptr<DocumentStore> document_store_;
FakeClock fake_clock_;
};
SectionIdMask CreateSectionIdMask(const std::vector<SectionId>& section_ids) {
SectionIdMask mask = 0;
for (SectionId section_id : section_ids) {
mask |= (UINT64_C(1) << section_id);
}
return mask;
}
SearchSpecProto CreateSearchSpec(TermMatchType::Code match_type) {
SearchSpecProto search_spec;
search_spec.set_term_match_type(match_type);
return search_spec;
}
ScoringSpecProto CreateScoringSpec(bool is_descending_order) {
ScoringSpecProto scoring_spec;
scoring_spec.set_order_by(is_descending_order ? ScoringSpecProto::Order::DESC
: ScoringSpecProto::Order::ASC);
return scoring_spec;
}
ResultSpecProto CreateResultSpec(int num_per_page) {
ResultSpecProto result_spec;
result_spec.set_num_per_page(num_per_page);
return result_spec;
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionTopLevelLeadNodeFieldPath) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("name");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results only contain the 'name' property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionNestedLeafNodeFieldPath) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("sender.name");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results only contain the 'sender.name'
// property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionIntermediateNodeFieldPath) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("sender");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results only contain the 'sender'
// property and all of the subproperties of 'sender'.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionMultipleNestedFieldPaths) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("sender.name");
type_property_mask->add_paths("sender.emailAddress");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results only contain the 'sender.name' and
// 'sender.address' properties.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionEmptyFieldPath) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results contain *no* properties.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one = DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionInvalidFieldPath) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("nonExistentProperty");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results contain *no* properties.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one = DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionValidAndInvalidFieldPath) {
// 1. Add two Email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("name");
type_property_mask->add_paths("nonExistentProperty");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned results only contain the 'name' property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionMultipleTypesNoWildcards) {
// 1. Add two documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask = result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("name");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
//*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned Email results only contain the 'name'
// property and the returned Person results have all of their properties.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionMultipleTypesWildcard) {
// 1. Add two documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* wildcard_type_property_mask =
result_spec.add_type_property_masks();
wildcard_type_property_mask->set_schema_type(
std::string(ProjectionTree::kSchemaTypeWildcard));
wildcard_type_property_mask->add_paths("name");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned Email results only contain the 'name'
// property and the returned Person results only contain the 'name' property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest,
ProjectionMultipleTypesWildcardWithOneOverride) {
// 1. Add two documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* email_type_property_mask =
result_spec.add_type_property_masks();
email_type_property_mask->set_schema_type("Email");
email_type_property_mask->add_paths("body");
TypePropertyMask* wildcard_type_property_mask =
result_spec.add_type_property_masks();
wildcard_type_property_mask->set_schema_type(
std::string(ProjectionTree::kSchemaTypeWildcard));
wildcard_type_property_mask->add_paths("name");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned Email results only contain the 'body'
// property and the returned Person results only contain the 'name' property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest,
ProjectionSingleTypesWildcardAndOverride) {
// 1. Add two documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Person")
.AddStringProperty("name", "Mr. Body")
.AddStringProperty("emailAddress", "mr.body123@gmail.com")
.Build())
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* email_type_property_mask =
result_spec.add_type_property_masks();
email_type_property_mask->set_schema_type("Email");
email_type_property_mask->add_paths("sender.name");
TypePropertyMask* wildcard_type_property_mask =
result_spec.add_type_property_masks();
wildcard_type_property_mask->set_schema_type(
std::string(ProjectionTree::kSchemaTypeWildcard));
wildcard_type_property_mask->add_paths("name");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned Email results only contain the 'sender.name'
// property and the returned Person results only contain the 'name' property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Person")
.AddStringProperty("name", "Mr. Body")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest,
ProjectionSingleTypesWildcardAndOverrideNestedProperty) {
// 1. Add two documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Person")
.AddStringProperty("name", "Mr. Body")
.AddStringProperty("emailAddress", "mr.body123@gmail.com")
.Build())
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
document_store_->Put(document_one));
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
document_store_->Put(document_two));
// 2. Setup the scored results.
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "name"),
GetSectionId("Email", "body")};
SectionIdMask hit_section_id_mask = CreateSectionIdMask(hit_section_ids);
std::vector<ScoredDocumentHit> scored_document_hits = {
{document_id1, hit_section_id_mask, /*score=*/0},
{document_id2, hit_section_id_mask, /*score=*/0}};
// 3. Create a ResultSpec with type property mask.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* email_type_property_mask =
result_spec.add_type_property_masks();
email_type_property_mask->set_schema_type("Email");
email_type_property_mask->add_paths("sender.name");
TypePropertyMask* wildcard_type_property_mask =
result_spec.add_type_property_masks();
wildcard_type_property_mask->set_schema_type(
std::string(ProjectionTree::kSchemaTypeWildcard));
wildcard_type_property_mask->add_paths("sender");
// 4. Create ResultState with custom ResultSpec.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 5. Verify that the returned Email results only contain the 'sender.name'
// property and the returned Person results contain no properties.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Person")
.AddStringProperty("name", "Mr. Body")
.Build())
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_document_one));
DocumentProto projected_document_two = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.Build();
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(projected_document_two));
}
TEST_F(ResultRetrieverV2ProjectionTest, ProjectionJoinDocuments) {
// 1. Add one Person document
DocumentProto person_document =
DocumentBuilder()
.SetKey("namespace", "Person/1")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId person_document_id,
document_store_->Put(person_document));
// 2. Add two Email documents
DocumentProto email_document1 =
DocumentBuilder()
.SetKey("namespace", "Email/1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId email_document_id1,
document_store_->Put(email_document1));
DocumentProto email_document2 =
DocumentBuilder()
.SetKey("namespace", "Email/2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("name", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId email_document_id2,
document_store_->Put(email_document2));
// 3. Setup the joined scored results.
std::vector<SectionId> person_hit_section_ids = {
GetSectionId("Person", "name")};
std::vector<SectionId> email_hit_section_ids = {
GetSectionId("Email", "name"), GetSectionId("Email", "body")};
SectionIdMask person_hit_section_id_mask =
CreateSectionIdMask(person_hit_section_ids);
SectionIdMask email_hit_section_id_mask =
CreateSectionIdMask(email_hit_section_ids);
ScoredDocumentHit person_scored_doc_hit(
person_document_id, person_hit_section_id_mask, /*score=*/0);
ScoredDocumentHit email1_scored_doc_hit(
email_document_id1, email_hit_section_id_mask, /*score=*/0);
ScoredDocumentHit email2_scored_doc_hit(
email_document_id2, email_hit_section_id_mask, /*score=*/0);
// Create JoinedScoredDocumentHits mapping Person to Email1 and Email2
std::vector<JoinedScoredDocumentHit> joined_scored_document_hits = {
JoinedScoredDocumentHit(
/*final_score=*/0,
/*parent_scored_document_hit=*/person_scored_doc_hit,
/*child_scored_document_hits=*/
{email1_scored_doc_hit, email2_scored_doc_hit})};
// 4. Create parent ResultSpec with type property mask.
ResultSpecProto parent_result_spec = CreateResultSpec(/*num_per_page=*/2);
TypePropertyMask* type_property_mask =
parent_result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Person");
type_property_mask->add_paths("name");
// 5. Create child ResultSpec with type property mask.
ResultSpecProto child_result_spec;
type_property_mask = child_result_spec.add_type_property_masks();
type_property_mask->set_schema_type("Email");
type_property_mask->add_paths("body");
// 6. Create ResultState with custom ResultSpecs.
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<JoinedScoredDocumentHit>>(
std::move(joined_scored_document_hits), /*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), parent_result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), child_result_spec,
SectionRestrictQueryTermsMap()),
parent_result_spec, *document_store_);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// 7. Verify that the returned results:
// - Person docs only contain the "name" property.
// - Email docs only contain the "body" property.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(1));
// Check parent doc.
DocumentProto projected_person_document =
DocumentBuilder()
.SetKey("namespace", "Person/1")
.SetCreationTimestampMs(1000)
.SetSchema("Person")
.AddStringProperty("name", "Joe Fox")
.Build();
EXPECT_THAT(page_result.results.at(0).document(),
EqualsProto(projected_person_document));
// Check child docs.
ASSERT_THAT(page_result.results.at(0).joined_results(), SizeIs(2));
// Check Email1
DocumentProto projected_email_document1 =
DocumentBuilder()
.SetKey("namespace", "Email/1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
EXPECT_THAT(page_result.results.at(0).joined_results(0).document(),
EqualsProto(projected_email_document1));
// Check Email2
DocumentProto projected_email_document2 =
DocumentBuilder()
.SetKey("namespace", "Email/2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
EXPECT_THAT(page_result.results.at(0).joined_results(1).document(),
EqualsProto(projected_email_document2));
}
} // namespace
} // namespace lib
} // namespace icing