blob: e56f10c7b3ea544abed84ed8fee1910c0c6f74b9 [file] [edit]
// Copyright (C) 2019 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/scoring/scorer-factory.h"
#include <memory>
#include <unordered_map>
#include "icing/text_classifier/lib3/utils/base/statusor.h"
#include "icing/absl_ports/canonical_errors.h"
#include "icing/index/hit/doc-hit-info.h"
#include "icing/index/iterator/doc-hit-info-iterator.h"
#include "icing/proto/scoring.pb.h"
#include "icing/scoring/advanced_scoring/advanced-scorer.h"
#include "icing/scoring/bm25f-calculator.h"
#include "icing/scoring/scorer.h"
#include "icing/scoring/section-weights.h"
#include "icing/store/document-id.h"
#include "icing/store/document-store.h"
#include "icing/util/status-macros.h"
namespace icing {
namespace lib {
class DocumentScoreScorer : public Scorer {
public:
explicit DocumentScoreScorer(const DocumentStore* document_store,
double default_score)
: document_store_(*document_store), default_score_(default_score) {}
double GetScore(const DocHitInfo& hit_info,
const DocHitInfoIterator*) override {
ICING_ASSIGN_OR_RETURN(
DocumentAssociatedScoreData score_data,
document_store_.GetDocumentAssociatedScoreData(hit_info.document_id()),
default_score_);
return static_cast<double>(score_data.document_score());
}
private:
const DocumentStore& document_store_;
double default_score_;
};
class DocumentCreationTimestampScorer : public Scorer {
public:
explicit DocumentCreationTimestampScorer(const DocumentStore* document_store,
double default_score)
: document_store_(*document_store), default_score_(default_score) {}
double GetScore(const DocHitInfo& hit_info,
const DocHitInfoIterator*) override {
ICING_ASSIGN_OR_RETURN(
DocumentAssociatedScoreData score_data,
document_store_.GetDocumentAssociatedScoreData(hit_info.document_id()),
default_score_);
return static_cast<double>(score_data.creation_timestamp_ms());
}
private:
const DocumentStore& document_store_;
double default_score_;
};
class RelevanceScoreScorer : public Scorer {
public:
explicit RelevanceScoreScorer(
std::unique_ptr<SectionWeights> section_weights,
std::unique_ptr<Bm25fCalculator> bm25f_calculator, double default_score)
: section_weights_(std::move(section_weights)),
bm25f_calculator_(std::move(bm25f_calculator)),
default_score_(default_score) {}
void PrepareToScore(
std::unordered_map<std::string, std::unique_ptr<DocHitInfoIterator>>*
query_term_iterators) override {
bm25f_calculator_->PrepareToScore(query_term_iterators);
}
double GetScore(const DocHitInfo& hit_info,
const DocHitInfoIterator* query_it) override {
if (!query_it) {
return default_score_;
}
return static_cast<double>(
bm25f_calculator_->ComputeScore(query_it, hit_info, default_score_));
}
private:
std::unique_ptr<SectionWeights> section_weights_;
std::unique_ptr<Bm25fCalculator> bm25f_calculator_;
double default_score_;
};
// A scorer which assigns scores to documents based on usage reports.
class UsageScorer : public Scorer {
public:
UsageScorer(const DocumentStore* document_store,
ScoringSpecProto::RankingStrategy::Code ranking_strategy,
double default_score, int64_t current_time_ms)
: document_store_(*document_store),
ranking_strategy_(ranking_strategy),
default_score_(default_score),
current_time_ms_(current_time_ms) {}
double GetScore(const DocHitInfo& hit_info,
const DocHitInfoIterator*) override {
std::optional<UsageStore::UsageScores> usage_scores =
document_store_.GetUsageScores(hit_info.document_id(),
current_time_ms_);
if (!usage_scores) {
// If there's no UsageScores entry present for this doc, then just
// treat it as a default instance.
usage_scores = UsageStore::UsageScores();
}
switch (ranking_strategy_) {
case ScoringSpecProto::RankingStrategy::USAGE_TYPE1_COUNT:
return usage_scores->usage_type1_count;
case ScoringSpecProto::RankingStrategy::USAGE_TYPE2_COUNT:
return usage_scores->usage_type2_count;
case ScoringSpecProto::RankingStrategy::USAGE_TYPE3_COUNT:
return usage_scores->usage_type3_count;
case ScoringSpecProto::RankingStrategy::USAGE_TYPE1_LAST_USED_TIMESTAMP:
return usage_scores->usage_type1_last_used_timestamp_s * 1000.0;
case ScoringSpecProto::RankingStrategy::USAGE_TYPE2_LAST_USED_TIMESTAMP:
return usage_scores->usage_type2_last_used_timestamp_s * 1000.0;
case ScoringSpecProto::RankingStrategy::USAGE_TYPE3_LAST_USED_TIMESTAMP:
return usage_scores->usage_type3_last_used_timestamp_s * 1000.0;
default:
// This shouldn't happen if this scorer is used correctly.
return default_score_;
}
}
private:
const DocumentStore& document_store_;
ScoringSpecProto::RankingStrategy::Code ranking_strategy_;
double default_score_;
int64_t current_time_ms_;
};
// A special scorer which does nothing but assigns the default score to each
// document. This is used especially when no scoring is required in a query.
class NoScorer : public Scorer {
public:
explicit NoScorer(double default_score) : default_score_(default_score) {}
double GetScore(const DocHitInfo& hit_info,
const DocHitInfoIterator*) override {
return default_score_;
}
private:
double default_score_;
};
namespace scorer_factory {
libtextclassifier3::StatusOr<std::unique_ptr<Scorer>> Create(
const ScoringSpecProto& scoring_spec, double default_score,
const DocumentStore* document_store, const SchemaStore* schema_store,
int64_t current_time_ms, const JoinChildrenFetcher* join_children_fetcher) {
ICING_RETURN_ERROR_IF_NULL(document_store);
ICING_RETURN_ERROR_IF_NULL(schema_store);
if (!scoring_spec.advanced_scoring_expression().empty() &&
scoring_spec.rank_by() !=
ScoringSpecProto::RankingStrategy::ADVANCED_SCORING_EXPRESSION) {
return absl_ports::InvalidArgumentError(
"Advanced scoring is not enabled, but the advanced scoring expression "
"is not empty!");
}
switch (scoring_spec.rank_by()) {
case ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE:
return std::make_unique<DocumentScoreScorer>(document_store,
default_score);
case ScoringSpecProto::RankingStrategy::CREATION_TIMESTAMP:
return std::make_unique<DocumentCreationTimestampScorer>(document_store,
default_score);
case ScoringSpecProto::RankingStrategy::RELEVANCE_SCORE: {
ICING_ASSIGN_OR_RETURN(
std::unique_ptr<SectionWeights> section_weights,
SectionWeights::Create(schema_store, scoring_spec));
auto bm25f_calculator = std::make_unique<Bm25fCalculator>(
document_store, section_weights.get(), current_time_ms);
return std::make_unique<RelevanceScoreScorer>(std::move(section_weights),
std::move(bm25f_calculator),
default_score);
}
case ScoringSpecProto::RankingStrategy::USAGE_TYPE1_COUNT:
[[fallthrough]];
case ScoringSpecProto::RankingStrategy::USAGE_TYPE2_COUNT:
[[fallthrough]];
case ScoringSpecProto::RankingStrategy::USAGE_TYPE3_COUNT:
[[fallthrough]];
case ScoringSpecProto::RankingStrategy::USAGE_TYPE1_LAST_USED_TIMESTAMP:
[[fallthrough]];
case ScoringSpecProto::RankingStrategy::USAGE_TYPE2_LAST_USED_TIMESTAMP:
[[fallthrough]];
case ScoringSpecProto::RankingStrategy::USAGE_TYPE3_LAST_USED_TIMESTAMP:
return std::make_unique<UsageScorer>(document_store,
scoring_spec.rank_by(),
default_score, current_time_ms);
case ScoringSpecProto::RankingStrategy::ADVANCED_SCORING_EXPRESSION:
if (scoring_spec.advanced_scoring_expression().empty()) {
return absl_ports::InvalidArgumentError(
"Advanced scoring is enabled, but the expression is empty!");
}
return AdvancedScorer::Create(scoring_spec, default_score, document_store,
schema_store, current_time_ms,
join_children_fetcher);
case ScoringSpecProto::RankingStrategy::JOIN_AGGREGATE_SCORE:
// Use join aggregate score to rank. Since the aggregation score is
// calculated by child documents after joining (in JoinProcessor), we can
// simply use NoScorer for parent documents.
[[fallthrough]];
case ScoringSpecProto::RankingStrategy::NONE:
return std::make_unique<NoScorer>(default_score);
}
}
} // namespace scorer_factory
} // namespace lib
} // namespace icing