blob: b32800c49b6d87a3233cb55a31bb8d81a596935c [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 <limits>
#include <memory>
#include <string_view>
#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/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 "icing/util/snippet-helpers.h"
#include "unicode/uloc.h"
namespace icing {
namespace lib {
namespace {
using ::icing::lib::portable_equals_proto::EqualsProto;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::SizeIs;
class ResultRetrieverV2SnippetTest : public testing::Test {
protected:
ResultRetrieverV2SnippetTest() : 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("subject")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_EXACT,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty(
PropertyConfigBuilder()
.SetName("name")
.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_;
};
ResultSpecProto::SnippetSpecProto CreateSnippetSpec() {
ResultSpecProto::SnippetSpecProto snippet_spec;
snippet_spec.set_num_to_snippet(std::numeric_limits<int>::max());
snippet_spec.set_num_matches_per_property(std::numeric_limits<int>::max());
snippet_spec.set_max_window_utf32_length(1024);
return snippet_spec;
}
DocumentProto CreateEmailDocument(int id) {
return DocumentBuilder()
.SetKey("icing", "Email/" + std::to_string(id))
.SetSchema("Email")
.AddStringProperty("subject", "subject foo " + std::to_string(id))
.AddStringProperty("body", "body bar " + std::to_string(id))
.SetCreationTimestampMs(1574365086666 + id)
.Build();
}
DocumentProto CreatePersonDocument(int id) {
return DocumentBuilder()
.SetKey("icing", "Person/" + std::to_string(id))
.SetSchema("Person")
.AddStringProperty("name", "person " + std::to_string(id))
.SetCreationTimestampMs(1574365086666 + id)
.Build();
}
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(ResultRetrieverV2SnippetTest,
DefaultSnippetSpecShouldDisableSnippeting) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<ScoredDocumentHit>>(
std::move(scored_document_hits), /*is_descending=*/true),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/true), result_spec,
SectionRestrictQueryTermsMap()),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.results.at(0).snippet(),
EqualsProto(SnippetProto::default_instance()));
EXPECT_THAT(page_result.results.at(1).snippet(),
EqualsProto(SnippetProto::default_instance()));
EXPECT_THAT(page_result.results.at(2).snippet(),
EqualsProto(SnippetProto::default_instance()));
EXPECT_THAT(page_result.num_results_with_snippets, Eq(0));
}
TEST_F(ResultRetrieverV2SnippetTest, SimpleSnippeted) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create ResultSpec with custom snippet spec.
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
*result_spec.mutable_snippet_spec() = CreateSnippetSpec();
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({{"", {"foo", "bar"}}})),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.num_results_with_snippets, Eq(3));
const DocumentProto& result_document_one =
page_result.results.at(0).document();
const SnippetProto& result_snippet_one = page_result.results.at(0).snippet();
EXPECT_THAT(result_document_one, EqualsProto(CreateEmailDocument(/*id=*/1)));
EXPECT_THAT(result_snippet_one.entries(), SizeIs(2));
EXPECT_THAT(result_snippet_one.entries(0).property_name(), Eq("body"));
std::string_view content = GetString(
&result_document_one, result_snippet_one.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_snippet_one.entries(0)),
ElementsAre("body bar 1"));
EXPECT_THAT(GetMatches(content, result_snippet_one.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_snippet_one.entries(1).property_name(), Eq("subject"));
content = GetString(&result_document_one,
result_snippet_one.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_snippet_one.entries(1)),
ElementsAre("subject foo 1"));
EXPECT_THAT(GetMatches(content, result_snippet_one.entries(1)),
ElementsAre("foo"));
const DocumentProto& result_document_two =
page_result.results.at(1).document();
const SnippetProto& result_snippet_two = page_result.results.at(1).snippet();
EXPECT_THAT(result_document_two, EqualsProto(CreateEmailDocument(/*id=*/2)));
EXPECT_THAT(result_snippet_two.entries(), SizeIs(2));
EXPECT_THAT(result_snippet_two.entries(0).property_name(), Eq("body"));
content = GetString(&result_document_two,
result_snippet_two.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_snippet_two.entries(0)),
ElementsAre("body bar 2"));
EXPECT_THAT(GetMatches(content, result_snippet_two.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_snippet_two.entries(1).property_name(), Eq("subject"));
content = GetString(&result_document_two,
result_snippet_two.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_snippet_two.entries(1)),
ElementsAre("subject foo 2"));
EXPECT_THAT(GetMatches(content, result_snippet_two.entries(1)),
ElementsAre("foo"));
const DocumentProto& result_document_three =
page_result.results.at(2).document();
const SnippetProto& result_snippet_three =
page_result.results.at(2).snippet();
EXPECT_THAT(result_document_three,
EqualsProto(CreateEmailDocument(/*id=*/3)));
EXPECT_THAT(result_snippet_three.entries(), SizeIs(2));
EXPECT_THAT(result_snippet_three.entries(0).property_name(), Eq("body"));
content = GetString(&result_document_three,
result_snippet_three.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_snippet_three.entries(0)),
ElementsAre("body bar 3"));
EXPECT_THAT(GetMatches(content, result_snippet_three.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_snippet_three.entries(1).property_name(), Eq("subject"));
content = GetString(&result_document_three,
result_snippet_three.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_snippet_three.entries(1)),
ElementsAre("subject foo 3"));
EXPECT_THAT(GetMatches(content, result_snippet_three.entries(1)),
ElementsAre("foo"));
}
TEST_F(ResultRetrieverV2SnippetTest, OnlyOneDocumentSnippeted) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto snippet_spec = CreateSnippetSpec();
snippet_spec.set_num_to_snippet(1);
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
*result_spec.mutable_snippet_spec() = std::move(snippet_spec);
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({{"", {"foo", "bar"}}})),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.num_results_with_snippets, Eq(1));
const DocumentProto& result_document = page_result.results.at(0).document();
const SnippetProto& result_snippet = page_result.results.at(0).snippet();
EXPECT_THAT(result_document, EqualsProto(CreateEmailDocument(/*id=*/1)));
EXPECT_THAT(result_snippet.entries(), SizeIs(2));
EXPECT_THAT(result_snippet.entries(0).property_name(), Eq("body"));
std::string_view content =
GetString(&result_document, result_snippet.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_snippet.entries(0)),
ElementsAre("body bar 1"));
EXPECT_THAT(GetMatches(content, result_snippet.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_snippet.entries(1).property_name(), Eq("subject"));
content =
GetString(&result_document, result_snippet.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_snippet.entries(1)),
ElementsAre("subject foo 1"));
EXPECT_THAT(GetMatches(content, result_snippet.entries(1)),
ElementsAre("foo"));
EXPECT_THAT(page_result.results.at(1).document(),
EqualsProto(CreateEmailDocument(/*id=*/2)));
EXPECT_THAT(page_result.results.at(1).snippet(),
EqualsProto(SnippetProto::default_instance()));
EXPECT_THAT(page_result.results.at(2).document(),
EqualsProto(CreateEmailDocument(/*id=*/3)));
EXPECT_THAT(page_result.results.at(2).snippet(),
EqualsProto(SnippetProto::default_instance()));
}
TEST_F(ResultRetrieverV2SnippetTest, ShouldSnippetAllResults) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto snippet_spec = CreateSnippetSpec();
snippet_spec.set_num_to_snippet(5);
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
*result_spec.mutable_snippet_spec() = std::move(snippet_spec);
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({{"", {"foo", "bar"}}})),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
// num_to_snippet = 5, num_previously_returned_in = 0,
// We can return 5 - 0 = 5 snippets at most. We're able to return all 3
// snippets here.
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.results.at(0).snippet().entries(), Not(IsEmpty()));
EXPECT_THAT(page_result.results.at(1).snippet().entries(), Not(IsEmpty()));
EXPECT_THAT(page_result.results.at(2).snippet().entries(), Not(IsEmpty()));
EXPECT_THAT(page_result.num_results_with_snippets, Eq(3));
}
TEST_F(ResultRetrieverV2SnippetTest, ShouldSnippetSomeResults) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto snippet_spec = CreateSnippetSpec();
snippet_spec.set_num_to_snippet(5);
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
*result_spec.mutable_snippet_spec() = std::move(snippet_spec);
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({{"", {"foo", "bar"}}})),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
{
absl_ports::unique_lock l(&result_state.mutex);
// Set remaining_num_to_snippet = 2
result_state.parent_adjustment_info()->remaining_num_to_snippet = 2;
}
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.results.at(0).snippet().entries(), Not(IsEmpty()));
EXPECT_THAT(page_result.results.at(1).snippet().entries(), Not(IsEmpty()));
EXPECT_THAT(page_result.results.at(2).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.num_results_with_snippets, Eq(2));
}
TEST_F(ResultRetrieverV2SnippetTest, ShouldNotSnippetAnyResults) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto snippet_spec = CreateSnippetSpec();
snippet_spec.set_num_to_snippet(5);
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
*result_spec.mutable_snippet_spec() = std::move(snippet_spec);
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({{"", {"foo", "bar"}}})),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
{
absl_ports::unique_lock l(&result_state.mutex);
// Set remaining_num_to_snippet = 0
result_state.parent_adjustment_info()->remaining_num_to_snippet = 0;
}
// We can't return any snippets for this page.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.results.at(0).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.results.at(1).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.results.at(2).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.num_results_with_snippets, Eq(0));
}
TEST_F(ResultRetrieverV2SnippetTest,
ShouldNotSnippetAnyResultsForNonPositiveNumMatchesPerProperty) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> hit_section_ids = {GetSectionId("Email", "subject"),
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},
{document_id3, hit_section_id_mask, /*score=*/0}};
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto snippet_spec = CreateSnippetSpec();
snippet_spec.set_num_to_snippet(5);
ResultSpecProto result_spec = CreateResultSpec(/*num_per_page=*/3);
*result_spec.mutable_snippet_spec() = std::move(snippet_spec);
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({{"", {"foo", "bar"}}})),
/*child_adjustment_info=*/nullptr, result_spec, *document_store_);
{
absl_ports::unique_lock l(&result_state.mutex);
// Set num_matchers_per_property = 0
result_state.parent_adjustment_info()
->snippet_context.snippet_spec.set_num_matches_per_property(0);
}
// We can't return any snippets for this page even though num_to_snippet > 0.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.results.at(0).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.results.at(1).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.results.at(2).snippet().entries(), IsEmpty());
EXPECT_THAT(page_result.num_results_with_snippets, Eq(0));
}
TEST_F(ResultRetrieverV2SnippetTest, JoinSnippeted) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id1,
document_store_->Put(CreatePersonDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id2,
document_store_->Put(CreatePersonDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id3,
document_store_->Put(CreatePersonDocument(/*id=*/3)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> person_hit_section_ids = {
GetSectionId("Person", "name")};
std::vector<SectionId> email_hit_section_ids = {
GetSectionId("Email", "subject"), 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 person1_scored_doc_hit(
person_document_id1, person_hit_section_id_mask, /*score=*/0);
ScoredDocumentHit person2_scored_doc_hit(
person_document_id2, person_hit_section_id_mask, /*score=*/0);
ScoredDocumentHit person3_scored_doc_hit(
person_document_id3, 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);
ScoredDocumentHit email3_scored_doc_hit(
email_document_id3, email_hit_section_id_mask, /*score=*/0);
// Create JoinedScoredDocumentHits mapping:
// - Person1 to Email1 and Email2
// - Person2 to empty
// - Person3 to Email3
JoinedScoredDocumentHit joined_scored_document_hit1(
/*final_score=*/0, /*parent_scored_document_hit=*/person1_scored_doc_hit,
/*child_scored_document_hits=*/
{email1_scored_doc_hit, email2_scored_doc_hit});
JoinedScoredDocumentHit joined_scored_document_hit2(
/*final_score=*/0, /*parent_scored_document_hit=*/person2_scored_doc_hit,
/*child_scored_document_hits=*/{});
JoinedScoredDocumentHit joined_scored_document_hit3(
/*final_score=*/0, /*parent_scored_document_hit=*/person3_scored_doc_hit,
/*child_scored_document_hits=*/{email3_scored_doc_hit});
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create parent ResultSpec with custom snippet spec.
ResultSpecProto parent_result_spec = CreateResultSpec(/*num_per_page=*/3);
*parent_result_spec.mutable_snippet_spec() = CreateSnippetSpec();
// Create child ResultSpec with custom snippet spec.
ResultSpecProto child_result_spec;
*child_result_spec.mutable_snippet_spec() = CreateSnippetSpec();
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<JoinedScoredDocumentHit>>(
std::vector<JoinedScoredDocumentHit>{joined_scored_document_hit1,
joined_scored_document_hit2,
joined_scored_document_hit3},
/*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), parent_result_spec,
SectionRestrictQueryTermsMap({{"", {"person"}}})),
/*child_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), child_result_spec,
SectionRestrictQueryTermsMap({{"", {"foo", "bar"}}})),
parent_result_spec, *document_store_);
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(3));
EXPECT_THAT(page_result.num_results_with_snippets, Eq(3));
// Result1: Person1 for parent and [Email1, Email2] for children.
// Check parent doc (Person1).
const DocumentProto& result_parent_document_one =
page_result.results.at(0).document();
const SnippetProto& result_parent_snippet_one =
page_result.results.at(0).snippet();
EXPECT_THAT(result_parent_document_one,
EqualsProto(CreatePersonDocument(/*id=*/1)));
ASSERT_THAT(result_parent_snippet_one.entries(), SizeIs(1));
EXPECT_THAT(result_parent_snippet_one.entries(0).property_name(), Eq("name"));
std::string_view content =
GetString(&result_parent_document_one,
result_parent_snippet_one.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_parent_snippet_one.entries(0)),
ElementsAre("person 1"));
EXPECT_THAT(GetMatches(content, result_parent_snippet_one.entries(0)),
ElementsAre("person"));
// Check child docs.
ASSERT_THAT(page_result.results.at(0).joined_results(), SizeIs(2));
// Check Email1.
const DocumentProto& result_child_document_one =
page_result.results.at(0).joined_results(0).document();
const SnippetProto& result_child_snippet_one =
page_result.results.at(0).joined_results(0).snippet();
EXPECT_THAT(result_child_document_one,
EqualsProto(CreateEmailDocument(/*id=*/1)));
ASSERT_THAT(result_child_snippet_one.entries(), SizeIs(2));
EXPECT_THAT(result_child_snippet_one.entries(0).property_name(), Eq("body"));
content = GetString(&result_child_document_one,
result_child_snippet_one.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_child_snippet_one.entries(0)),
ElementsAre("body bar 1"));
EXPECT_THAT(GetMatches(content, result_child_snippet_one.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_child_snippet_one.entries(1).property_name(),
Eq("subject"));
content = GetString(&result_child_document_one,
result_child_snippet_one.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_child_snippet_one.entries(1)),
ElementsAre("subject foo 1"));
EXPECT_THAT(GetMatches(content, result_child_snippet_one.entries(1)),
ElementsAre("foo"));
// Check Email2.
const DocumentProto& result_child_document_two =
page_result.results.at(0).joined_results(1).document();
const SnippetProto& result_child_snippet_two =
page_result.results.at(0).joined_results(1).snippet();
EXPECT_THAT(result_child_document_two,
EqualsProto(CreateEmailDocument(/*id=*/2)));
ASSERT_THAT(result_child_snippet_two.entries(), SizeIs(2));
EXPECT_THAT(result_child_snippet_two.entries(0).property_name(), Eq("body"));
content = GetString(&result_child_document_two,
result_child_snippet_two.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_child_snippet_two.entries(0)),
ElementsAre("body bar 2"));
EXPECT_THAT(GetMatches(content, result_child_snippet_two.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_child_snippet_two.entries(1).property_name(),
Eq("subject"));
content = GetString(&result_child_document_two,
result_child_snippet_two.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_child_snippet_two.entries(1)),
ElementsAre("subject foo 2"));
EXPECT_THAT(GetMatches(content, result_child_snippet_two.entries(1)),
ElementsAre("foo"));
// Result2: Person2 for parent and [] for children.
// Check parent doc (Person1).
const DocumentProto& result_parent_document_two =
page_result.results.at(1).document();
const SnippetProto& result_parent_snippet_two =
page_result.results.at(1).snippet();
EXPECT_THAT(result_parent_document_two,
EqualsProto(CreatePersonDocument(/*id=*/2)));
ASSERT_THAT(result_parent_snippet_two.entries(), SizeIs(1));
EXPECT_THAT(result_parent_snippet_two.entries(0).property_name(), Eq("name"));
content = GetString(&result_parent_document_two,
result_parent_snippet_two.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_parent_snippet_two.entries(0)),
ElementsAre("person 2"));
EXPECT_THAT(GetMatches(content, result_parent_snippet_two.entries(0)),
ElementsAre("person"));
// Check child docs.
ASSERT_THAT(page_result.results.at(1).joined_results(), IsEmpty());
// Result3: Person3 for parent and [Email3] for children.
// Check parent doc (Person3).
const DocumentProto& result_parent_document_three =
page_result.results.at(2).document();
const SnippetProto& result_parent_snippet_three =
page_result.results.at(2).snippet();
EXPECT_THAT(result_parent_document_three,
EqualsProto(CreatePersonDocument(/*id=*/3)));
ASSERT_THAT(result_parent_snippet_three.entries(), SizeIs(1));
EXPECT_THAT(result_parent_snippet_three.entries(0).property_name(),
Eq("name"));
content = GetString(&result_parent_document_three,
result_parent_snippet_three.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_parent_snippet_three.entries(0)),
ElementsAre("person 3"));
EXPECT_THAT(GetMatches(content, result_parent_snippet_three.entries(0)),
ElementsAre("person"));
// Check child docs.
ASSERT_THAT(page_result.results.at(2).joined_results(), SizeIs(1));
// Check Email3.
const DocumentProto& result_child_document_three =
page_result.results.at(2).joined_results(0).document();
const SnippetProto& result_child_snippet_three =
page_result.results.at(2).joined_results(0).snippet();
EXPECT_THAT(result_child_document_three,
EqualsProto(CreateEmailDocument(/*id=*/3)));
ASSERT_THAT(result_child_snippet_three.entries(), SizeIs(2));
EXPECT_THAT(result_child_snippet_three.entries(0).property_name(),
Eq("body"));
content = GetString(&result_child_document_three,
result_child_snippet_three.entries(0).property_name());
EXPECT_THAT(GetWindows(content, result_child_snippet_three.entries(0)),
ElementsAre("body bar 3"));
EXPECT_THAT(GetMatches(content, result_child_snippet_three.entries(0)),
ElementsAre("bar"));
EXPECT_THAT(result_child_snippet_three.entries(1).property_name(),
Eq("subject"));
content = GetString(&result_child_document_three,
result_child_snippet_three.entries(1).property_name());
EXPECT_THAT(GetWindows(content, result_child_snippet_three.entries(1)),
ElementsAre("subject foo 3"));
EXPECT_THAT(GetMatches(content, result_child_snippet_three.entries(1)),
ElementsAre("foo"));
}
TEST_F(ResultRetrieverV2SnippetTest, ShouldSnippetAllJoinedResults) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id1,
document_store_->Put(CreatePersonDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id2,
document_store_->Put(CreatePersonDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> person_hit_section_ids = {
GetSectionId("Person", "name")};
std::vector<SectionId> email_hit_section_ids = {
GetSectionId("Email", "subject"), 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 person1_scored_doc_hit(
person_document_id1, person_hit_section_id_mask, /*score=*/0);
ScoredDocumentHit person2_scored_doc_hit(
person_document_id2, 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);
ScoredDocumentHit email3_scored_doc_hit(
email_document_id3, email_hit_section_id_mask, /*score=*/0);
// Create JoinedScoredDocumentHits mapping:
// - Person1 to Email1
// - Person2 to Email2, Email3
JoinedScoredDocumentHit joined_scored_document_hit1(
/*final_score=*/0, /*parent_scored_document_hit=*/person1_scored_doc_hit,
/*child_scored_document_hits=*/
{email1_scored_doc_hit});
JoinedScoredDocumentHit joined_scored_document_hit2(
/*final_score=*/0, /*parent_scored_document_hit=*/person2_scored_doc_hit,
/*child_scored_document_hits=*/
{email2_scored_doc_hit, email3_scored_doc_hit});
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create parent ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto parent_snippet_spec = CreateSnippetSpec();
parent_snippet_spec.set_num_to_snippet(1);
ResultSpecProto parent_result_spec = CreateResultSpec(/*num_per_page=*/3);
*parent_result_spec.mutable_snippet_spec() = std::move(parent_snippet_spec);
// Create child ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto child_snippet_spec = CreateSnippetSpec();
child_snippet_spec.set_num_to_snippet(3);
ResultSpecProto child_result_spec;
*child_result_spec.mutable_snippet_spec() = std::move(child_snippet_spec);
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<JoinedScoredDocumentHit>>(
std::vector<JoinedScoredDocumentHit>{joined_scored_document_hit1,
joined_scored_document_hit2},
/*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), parent_result_spec,
SectionRestrictQueryTermsMap({{"", {"person"}}})),
/*child_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), child_result_spec,
SectionRestrictQueryTermsMap({{"", {"foo", "bar"}}})),
parent_result_spec, *document_store_);
// Only 1 parent document should be snippeted, but all of the child documents
// should be snippeted.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
// Result1: Person1 for parent and [Email1] for children.
// Check parent doc (Person1).
EXPECT_THAT(page_result.results.at(0).snippet().entries(), Not(IsEmpty()));
// Check child docs.
ASSERT_THAT(page_result.results.at(0).joined_results(), SizeIs(1));
EXPECT_THAT(page_result.results.at(0).joined_results(0).snippet().entries(),
Not(IsEmpty()));
// Result2: Person2 for parent and [Email2, Email3] for children.
// Check parent doc (Person2).
EXPECT_THAT(page_result.results.at(1).snippet().entries(), IsEmpty());
// Check child docs.
ASSERT_THAT(page_result.results.at(1).joined_results(), SizeIs(2));
EXPECT_THAT(page_result.results.at(1).joined_results(0).snippet().entries(),
Not(IsEmpty()));
EXPECT_THAT(page_result.results.at(1).joined_results(1).snippet().entries(),
Not(IsEmpty()));
EXPECT_THAT(page_result.num_results_with_snippets, Eq(1));
}
TEST_F(ResultRetrieverV2SnippetTest, ShouldSnippetSomeJoinedResults) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id1,
document_store_->Put(CreatePersonDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId person_document_id2,
document_store_->Put(CreatePersonDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id1,
document_store_->Put(CreateEmailDocument(/*id=*/1)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id2,
document_store_->Put(CreateEmailDocument(/*id=*/2)));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId email_document_id3,
document_store_->Put(CreateEmailDocument(/*id=*/3)));
std::vector<SectionId> person_hit_section_ids = {
GetSectionId("Person", "name")};
std::vector<SectionId> email_hit_section_ids = {
GetSectionId("Email", "subject"), 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 person1_scored_doc_hit(
person_document_id1, person_hit_section_id_mask, /*score=*/0);
ScoredDocumentHit person2_scored_doc_hit(
person_document_id2, 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);
ScoredDocumentHit email3_scored_doc_hit(
email_document_id3, email_hit_section_id_mask, /*score=*/0);
// Create JoinedScoredDocumentHits mapping:
// - Person1 to Email1
// - Person2 to Email2, Email3
JoinedScoredDocumentHit joined_scored_document_hit1(
/*final_score=*/0, /*parent_scored_document_hit=*/person1_scored_doc_hit,
/*child_scored_document_hits=*/
{email1_scored_doc_hit});
JoinedScoredDocumentHit joined_scored_document_hit2(
/*final_score=*/0, /*parent_scored_document_hit=*/person2_scored_doc_hit,
/*child_scored_document_hits=*/
{email2_scored_doc_hit, email3_scored_doc_hit});
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<ResultRetrieverV2> result_retriever,
ResultRetrieverV2::Create(document_store_.get(), schema_store_.get(),
language_segmenter_.get(), normalizer_.get()));
// Create parent ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto parent_snippet_spec = CreateSnippetSpec();
parent_snippet_spec.set_num_to_snippet(3);
ResultSpecProto parent_result_spec = CreateResultSpec(/*num_per_page=*/3);
*parent_result_spec.mutable_snippet_spec() = std::move(parent_snippet_spec);
// Create child ResultSpec with custom snippet spec.
ResultSpecProto::SnippetSpecProto child_snippet_spec = CreateSnippetSpec();
child_snippet_spec.set_num_to_snippet(2);
ResultSpecProto child_result_spec;
*child_result_spec.mutable_snippet_spec() = std::move(child_snippet_spec);
ResultStateV2 result_state(
std::make_unique<
PriorityQueueScoredDocumentHitsRanker<JoinedScoredDocumentHit>>(
std::vector<JoinedScoredDocumentHit>{joined_scored_document_hit1,
joined_scored_document_hit2},
/*is_descending=*/false),
/*parent_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), parent_result_spec,
SectionRestrictQueryTermsMap({{"", {"person"}}})),
/*child_adjustment_info=*/
std::make_unique<ResultAdjustmentInfo>(
CreateSearchSpec(TermMatchType::EXACT_ONLY),
CreateScoringSpec(/*is_descending_order=*/false), child_result_spec,
SectionRestrictQueryTermsMap({{"", {"foo", "bar"}}})),
parent_result_spec, *document_store_);
// All parents document should be snippeted. Only 2 child documents should be
// snippeted.
PageResult page_result =
result_retriever->RetrieveNextPage(result_state).first;
ASSERT_THAT(page_result.results, SizeIs(2));
// Result1: Person1 for parent and [Email1] for children.
// Check parent doc (Person1).
EXPECT_THAT(page_result.results.at(0).snippet().entries(), Not(IsEmpty()));
// Check child docs.
ASSERT_THAT(page_result.results.at(0).joined_results(), SizeIs(1));
EXPECT_THAT(page_result.results.at(0).joined_results(0).snippet().entries(),
Not(IsEmpty()));
// Result2: Person2 for parent and [Email2, Email3] for children.
// Check parent doc (Person2).
EXPECT_THAT(page_result.results.at(1).snippet().entries(), Not(IsEmpty()));
// Check child docs.
ASSERT_THAT(page_result.results.at(1).joined_results(), SizeIs(2));
EXPECT_THAT(page_result.results.at(1).joined_results(0).snippet().entries(),
Not(IsEmpty()));
EXPECT_THAT(page_result.results.at(1).joined_results(1).snippet().entries(),
IsEmpty());
EXPECT_THAT(page_result.num_results_with_snippets, Eq(2));
}
} // namespace
} // namespace lib
} // namespace icing