blob: e50262b4437ea69669b42add0f4140085f86066d [file]
// 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 "icing/result/result-retriever-v2.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "icing/text_classifier/lib3/utils/base/statusor.h"
#include "icing/absl_ports/mutex.h"
#include "icing/proto/document.pb.h"
#include "icing/proto/search.pb.h"
#include "icing/result/page-result.h"
#include "icing/result/projection-tree.h"
#include "icing/result/projector.h"
#include "icing/result/result-adjustment-info.h"
#include "icing/result/result-state-v2.h"
#include "icing/result/snippet-context.h"
#include "icing/result/snippet-retriever.h"
#include "icing/schema/schema-store.h"
#include "icing/schema/section.h"
#include "icing/scoring/scored-document-hit.h"
#include "icing/store/document-filter-data.h"
#include "icing/store/document-store.h"
#include "icing/store/namespace-id.h"
#include "icing/tokenization/language-segmenter.h"
#include "icing/transform/normalizer.h"
#include "icing/util/logging.h"
#include "icing/util/status-macros.h"
namespace icing {
namespace lib {
namespace {
void ApplyProjection(const ResultAdjustmentInfo* adjustment_info,
DocumentProto* document) {
if (adjustment_info == nullptr) {
return;
}
auto itr = adjustment_info->projection_tree_map.find(document->schema());
if (itr != adjustment_info->projection_tree_map.end()) {
projector::Project(itr->second.root().children, document);
} else {
auto wildcard_projection_tree_itr =
adjustment_info->projection_tree_map.find(
std::string(SchemaStore::kSchemaTypeWildcard));
if (wildcard_projection_tree_itr !=
adjustment_info->projection_tree_map.end()) {
projector::Project(wildcard_projection_tree_itr->second.root().children,
document);
}
}
}
bool ApplySnippet(ResultAdjustmentInfo* adjustment_info,
const SnippetRetriever& snippet_retriever,
const DocumentProto& document, SectionIdMask section_id_mask,
SearchResultProto::ResultProto* result) {
if (adjustment_info == nullptr) {
return false;
}
const SnippetContext& snippet_context = adjustment_info->snippet_context;
int& remaining_num_to_snippet = adjustment_info->remaining_num_to_snippet;
if (snippet_context.snippet_spec.num_matches_per_property() > 0 &&
remaining_num_to_snippet > 0) {
SnippetProto snippet_proto = snippet_retriever.RetrieveSnippet(
snippet_context.query_terms, snippet_context.match_type,
snippet_context.snippet_spec, document, section_id_mask);
*result->mutable_snippet() = std::move(snippet_proto);
--remaining_num_to_snippet;
return true;
}
return false;
}
} // namespace
bool GroupResultLimiterV2::ShouldBeRemoved(
const ScoredDocumentHit& scored_document_hit,
const std::unordered_map<int32_t, int>& entry_id_group_id_map,
const DocumentStore& document_store, std::vector<int>& group_result_limits,
ResultSpecProto::ResultGroupingType result_group_type,
int64_t current_time_ms) const {
auto document_filter_data_optional =
document_store.GetAliveDocumentFilterData(
scored_document_hit.document_id(), current_time_ms);
if (!document_filter_data_optional) {
// The document doesn't exist.
return true;
}
NamespaceId namespace_id =
document_filter_data_optional.value().namespace_id();
SchemaTypeId schema_type_id =
document_filter_data_optional.value().schema_type_id();
auto entry_id_or = document_store.GetResultGroupingEntryId(
result_group_type, namespace_id, schema_type_id);
if (!entry_id_or.ok()) {
return false;
}
int32_t entry_id = entry_id_or.ValueOrDie();
auto iter = entry_id_group_id_map.find(entry_id);
if (iter == entry_id_group_id_map.end()) {
// If a ResultGrouping Entry Id isn't found in entry_id_group_id_map, then
// there are no limits placed on results from this entry id.
return false;
}
int& count = group_result_limits.at(iter->second);
if (count <= 0) {
return true;
}
--count;
return false;
}
libtextclassifier3::StatusOr<std::unique_ptr<ResultRetrieverV2>>
ResultRetrieverV2::Create(
const DocumentStore* doc_store, const SchemaStore* schema_store,
const LanguageSegmenter* language_segmenter, const Normalizer* normalizer,
std::unique_ptr<const GroupResultLimiterV2> group_result_limiter) {
ICING_RETURN_ERROR_IF_NULL(doc_store);
ICING_RETURN_ERROR_IF_NULL(schema_store);
ICING_RETURN_ERROR_IF_NULL(language_segmenter);
ICING_RETURN_ERROR_IF_NULL(normalizer);
ICING_RETURN_ERROR_IF_NULL(group_result_limiter);
ICING_ASSIGN_OR_RETURN(
std::unique_ptr<SnippetRetriever> snippet_retriever,
SnippetRetriever::Create(schema_store, language_segmenter, normalizer));
return std::unique_ptr<ResultRetrieverV2>(
new ResultRetrieverV2(doc_store, std::move(snippet_retriever),
std::move(group_result_limiter)));
}
std::pair<PageResult, bool> ResultRetrieverV2::RetrieveNextPage(
ResultStateV2& result_state, int64_t current_time_ms) const {
absl_ports::unique_lock l(&result_state.mutex);
// For calculating page
int original_scored_document_hits_ranker_size =
result_state.scored_document_hits_ranker->size();
int num_results_with_snippets = 0;
// Retrieve info
std::vector<SearchResultProto::ResultProto> results;
int32_t num_total_bytes = 0;
while (results.size() < result_state.num_per_page() &&
!result_state.scored_document_hits_ranker->empty()) {
JoinedScoredDocumentHit next_best_document_hit =
result_state.scored_document_hits_ranker->PopNext();
if (group_result_limiter_->ShouldBeRemoved(
next_best_document_hit.parent_scored_document_hit(),
result_state.entry_id_group_id_map(), doc_store_,
result_state.group_result_limits, result_state.result_group_type(),
current_time_ms)) {
continue;
}
libtextclassifier3::StatusOr<DocumentProto> document_or = doc_store_.Get(
next_best_document_hit.parent_scored_document_hit().document_id());
if (!document_or.ok()) {
// Skip the document if getting errors.
ICING_LOG(WARNING) << "Fail to fetch document from document store: "
<< document_or.status().error_message();
continue;
}
DocumentProto document = std::move(document_or).ValueOrDie();
// Apply parent projection
ApplyProjection(result_state.parent_adjustment_info(), &document);
SearchResultProto::ResultProto result;
// Add parent snippet if requested.
if (ApplySnippet(result_state.parent_adjustment_info(), *snippet_retriever_,
document,
next_best_document_hit.parent_scored_document_hit()
.hit_section_id_mask(),
&result)) {
++num_results_with_snippets;
}
// Add the document, itself.
*result.mutable_document() = std::move(document);
result.set_score(next_best_document_hit.final_score());
const auto* parent_additional_scores =
next_best_document_hit.parent_scored_document_hit().additional_scores();
if (parent_additional_scores != nullptr) {
result.mutable_additional_scores()->Add(parent_additional_scores->begin(),
parent_additional_scores->end());
}
// Retrieve child documents
for (const ScoredDocumentHit& child_scored_document_hit :
next_best_document_hit.child_scored_document_hits()) {
if (result.joined_results_size() >=
result_state.max_joined_children_per_parent_to_return()) {
break;
}
libtextclassifier3::StatusOr<DocumentProto> child_document_or =
doc_store_.Get(child_scored_document_hit.document_id());
if (!child_document_or.ok()) {
// Skip the document if getting errors.
ICING_LOG(WARNING)
<< "Fail to fetch child document from document store: "
<< child_document_or.status().error_message();
continue;
}
DocumentProto child_document = std::move(child_document_or).ValueOrDie();
ApplyProjection(result_state.child_adjustment_info(), &child_document);
SearchResultProto::ResultProto* child_result =
result.add_joined_results();
// Add child snippet if requested.
ApplySnippet(result_state.child_adjustment_info(), *snippet_retriever_,
child_document,
child_scored_document_hit.hit_section_id_mask(),
child_result);
*child_result->mutable_document() = std::move(child_document);
child_result->set_score(child_scored_document_hit.score());
if (child_scored_document_hit.additional_scores() != nullptr) {
child_result->mutable_additional_scores()->Add(
child_scored_document_hit.additional_scores()->begin(),
child_scored_document_hit.additional_scores()->end());
}
}
size_t result_bytes = result.ByteSizeLong();
results.push_back(std::move(result));
// Check if num_total_bytes + result_bytes reaches or exceeds
// num_total_bytes_per_page_threshold. Use subtraction to avoid integer
// overflow.
if (result_bytes >=
result_state.num_total_bytes_per_page_threshold() - num_total_bytes) {
break;
}
num_total_bytes += result_bytes;
}
// Update numbers in ResultState
result_state.num_returned += results.size();
result_state.IncrementNumTotalHits(
result_state.scored_document_hits_ranker->size() -
original_scored_document_hits_ranker_size);
bool has_more_results = !result_state.scored_document_hits_ranker->empty();
return std::make_pair(
PageResult(std::move(results), num_results_with_snippets,
result_state.num_per_page()),
has_more_results);
}
} // namespace lib
} // namespace icing