1 // Copyright (C) 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef ICING_SCORING_SCORED_DOCUMENT_HIT_H_ 16 #define ICING_SCORING_SCORED_DOCUMENT_HIT_H_ 17 18 #include <type_traits> 19 20 #include "icing/legacy/core/icing-packed-pod.h" 21 #include "icing/schema/section.h" 22 #include "icing/store/document-id.h" 23 24 namespace icing { 25 namespace lib { 26 27 class JoinedScoredDocumentHit; 28 29 // A data class containing information about the document, hit sections, and a 30 // score. The score is calculated against both the document and the hit 31 // sections. 32 class ScoredDocumentHit { 33 public: 34 class Converter { 35 public: 36 JoinedScoredDocumentHit operator()( 37 ScoredDocumentHit&& scored_doc_hit) const; 38 }; 39 ScoredDocumentHit(DocumentId document_id,SectionIdMask hit_section_id_mask,double score)40 ScoredDocumentHit(DocumentId document_id, SectionIdMask hit_section_id_mask, 41 double score) 42 : document_id_(document_id), 43 hit_section_id_mask_(hit_section_id_mask), 44 score_(score) {} 45 46 bool operator<(const ScoredDocumentHit& other) const { 47 if (score() < other.score()) return true; 48 if (score() > other.score()) return false; 49 return document_id() < other.document_id(); 50 } 51 document_id()52 DocumentId document_id() const { return document_id_; } 53 hit_section_id_mask()54 SectionIdMask hit_section_id_mask() const { return hit_section_id_mask_; } 55 score()56 double score() const { return score_; } 57 58 private: 59 DocumentId document_id_; 60 SectionIdMask hit_section_id_mask_; 61 double score_; 62 } __attribute__((packed)); 63 64 static_assert(sizeof(ScoredDocumentHit) == 20, 65 "Size of ScoredDocHit should be 20"); 66 static_assert(icing_is_packed_pod<ScoredDocumentHit>::value, "go/icing-ubsan"); 67 68 // A custom comparator for ScoredDocumentHit that determines which 69 // ScoredDocumentHit is better (should come first) based off of 70 // ScoredDocumentHit itself and the order of its score. 71 // 72 // Returns true if left is better than right according to score and order. 73 // Comparison is based off of score with ties broken by 74 // ScoredDocumentHit.document_id(). 75 class ScoredDocumentHitComparator { 76 public: 77 explicit ScoredDocumentHitComparator(bool is_descending = true) is_descending_(is_descending)78 : is_descending_(is_descending) {} 79 operator()80 bool operator()(const ScoredDocumentHit& lhs, 81 const ScoredDocumentHit& rhs) const { 82 // STL comparator requirement: equal MUST return false. 83 // If writing `return is_descending_ == !(lhs < rhs)`: 84 // - When lhs == rhs, !(lhs < rhs) is true 85 // - If is_descending_ is true, then we return true for equal case! 86 if (is_descending_) { 87 return rhs < lhs; 88 } 89 return lhs < rhs; 90 } 91 92 private: 93 bool is_descending_; 94 }; 95 96 // A data class containing information about a composite document after joining, 97 // including final score, parent ScoredDocumentHit, and a vector of all child 98 // ScoredDocumentHits. The final score is calculated by the strategy specified 99 // in join spec/rank strategy. It could be aggregated score, raw parent doc 100 // score, or anything else. 101 // 102 // ScoredDocumentHitsRanker may store ScoredDocumentHit or 103 // JoinedScoredDocumentHit. 104 // - We could've created a virtual class for them and ScoredDocumentHitsRanker 105 // uses the abstract type. 106 // - However, Icing lib caches ScoredDocumentHitsRanker (which contains a list 107 // of (Joined)ScoredDocumentHits) in ResultState. Inheriting the virtual class 108 // makes both classes have additional 8 bytes for vtable, which increases 40% 109 // and 15% memory usage respectively. 110 // - Also since JoinedScoredDocumentHit is a super-set of ScoredDocumentHit, 111 // let's avoid the common virtual class and instead implement a convert 112 // function (original type -> JoinedScoredDocumentHit) for each class, so 113 // ScoredDocumentHitsRanker::PopNext can return a common type (i.e. 114 // JoinedScoredDocumentHit). 115 class JoinedScoredDocumentHit { 116 public: 117 class Converter { 118 public: operator()119 JoinedScoredDocumentHit operator()( 120 JoinedScoredDocumentHit&& scored_doc_hit) const { 121 return scored_doc_hit; 122 } 123 }; 124 JoinedScoredDocumentHit(double final_score,ScoredDocumentHit parent_scored_document_hit,std::vector<ScoredDocumentHit> child_scored_document_hits)125 explicit JoinedScoredDocumentHit( 126 double final_score, ScoredDocumentHit parent_scored_document_hit, 127 std::vector<ScoredDocumentHit> child_scored_document_hits) 128 : final_score_(final_score), 129 parent_scored_document_hit_(std::move(parent_scored_document_hit)), 130 child_scored_document_hits_(std::move(child_scored_document_hits)) {} 131 132 bool operator<(const JoinedScoredDocumentHit& other) const { 133 if (final_score_ != other.final_score_) { 134 return final_score_ < other.final_score_; 135 } 136 return parent_scored_document_hit_ < other.parent_scored_document_hit_; 137 } 138 final_score()139 double final_score() const { return final_score_; } 140 parent_scored_document_hit()141 const ScoredDocumentHit& parent_scored_document_hit() const { 142 return parent_scored_document_hit_; 143 } 144 child_scored_document_hits()145 const std::vector<ScoredDocumentHit>& child_scored_document_hits() const { 146 return child_scored_document_hits_; 147 } 148 149 private: 150 double final_score_; 151 ScoredDocumentHit parent_scored_document_hit_; 152 std::vector<ScoredDocumentHit> child_scored_document_hits_; 153 } __attribute__((packed)); 154 155 } // namespace lib 156 } // namespace icing 157 158 #endif // ICING_SCORING_SCORED_DOCUMENT_HIT_H_ 159