1 // Copyright (C) 2022 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 #include "icing/file/posting_list/posting-list-accessor.h"
16
17 #include <cstdint>
18 #include <memory>
19 #include <utility>
20
21 #include "icing/text_classifier/lib3/utils/base/status.h"
22 #include "icing/text_classifier/lib3/utils/base/statusor.h"
23 #include "icing/absl_ports/canonical_errors.h"
24 #include "icing/file/posting_list/flash-index-storage.h"
25 #include "icing/file/posting_list/posting-list-identifier.h"
26 #include "icing/file/posting_list/posting-list-used.h"
27 #include "icing/util/status-macros.h"
28
29 namespace icing {
30 namespace lib {
31
FlushPreexistingPostingList()32 libtextclassifier3::Status PostingListAccessor::FlushPreexistingPostingList() {
33 if (preexisting_posting_list_->posting_list.size_in_bytes() ==
34 storage_->max_posting_list_bytes()) {
35 // If this is a max-sized posting list, then sync to disk and keep track of
36 // the id.
37 ICING_RETURN_IF_ERROR(
38 storage_->WritePostingListToDisk(*preexisting_posting_list_));
39 prev_block_identifier_ = preexisting_posting_list_->id;
40 } else {
41 // If this is NOT a max-sized posting list, then our data have outgrown this
42 // particular posting list. Move the data into the in-memory posting list
43 // and free this posting list.
44 //
45 // Move will always succeed since in_memory_posting_list_ is max_pl_bytes.
46 ICING_RETURN_IF_ERROR(GetSerializer()->MoveFrom(
47 /*dst=*/&in_memory_posting_list_,
48 /*src=*/&preexisting_posting_list_->posting_list));
49
50 // Now that all the contents of this posting list have been copied, there's
51 // no more use for it. Make it available to be used for another posting
52 // list.
53 ICING_RETURN_IF_ERROR(
54 storage_->FreePostingList(std::move(*preexisting_posting_list_)));
55 }
56 preexisting_posting_list_.reset();
57 return libtextclassifier3::Status::OK;
58 }
59
FlushInMemoryPostingList()60 libtextclassifier3::Status PostingListAccessor::FlushInMemoryPostingList() {
61 // We exceeded max_pl_bytes(). Need to flush in_memory_posting_list_ and
62 // update the chain.
63 ICING_ASSIGN_OR_RETURN(PostingListHolder holder,
64 storage_->AllocateAndChainMaxSizePostingList(
65 prev_block_identifier_.block_index()));
66 ICING_RETURN_IF_ERROR(
67 GetSerializer()->MoveFrom(/*dst=*/&holder.posting_list,
68 /*src=*/&in_memory_posting_list_));
69 ICING_RETURN_IF_ERROR(storage_->WritePostingListToDisk(holder));
70
71 // Set prev block id only if persist to disk succeeded.
72 prev_block_identifier_ = holder.id;
73 return libtextclassifier3::Status::OK;
74 }
75
Finalize()76 PostingListAccessor::FinalizeResult PostingListAccessor::Finalize() && {
77 if (preexisting_posting_list_ != nullptr) {
78 // Sync to disk.
79 return FinalizeResult(
80 storage_->WritePostingListToDisk(*preexisting_posting_list_),
81 preexisting_posting_list_->id);
82 }
83
84 if (GetSerializer()->GetBytesUsed(&in_memory_posting_list_) <= 0) {
85 return FinalizeResult(absl_ports::InvalidArgumentError(
86 "Can't finalize an empty PostingListAccessor. "
87 "There's nothing to Finalize!"),
88 PostingListIdentifier::kInvalid);
89 }
90
91 libtextclassifier3::StatusOr<PostingListHolder> holder_or;
92 if (prev_block_identifier_.is_valid()) {
93 // If prev_block_identifier_ is valid, then it means there was a max-sized
94 // posting list, so we have to allocate another new max size posting list
95 // and chain them together.
96 holder_or = storage_->AllocateAndChainMaxSizePostingList(
97 prev_block_identifier_.block_index());
98 } else {
99 // Otherwise, it is the first posting list, and we can use smaller size pl.
100 // Note that even if it needs a max-sized posting list here, it is ok to
101 // call AllocatePostingList without setting next block index since we don't
102 // have any previous posting list to chain and AllocatePostingList will set
103 // next block index to kInvalidBlockIndex.
104 uint32_t posting_list_bytes =
105 GetSerializer()->GetMinPostingListSizeToFit(&in_memory_posting_list_);
106 holder_or = storage_->AllocatePostingList(posting_list_bytes);
107 }
108
109 if (!holder_or.ok()) {
110 return FinalizeResult(std::move(holder_or).status(),
111 prev_block_identifier_);
112 }
113 PostingListHolder holder = std::move(holder_or).ValueOrDie();
114
115 // Move to allocated area. This should never actually return an error. We know
116 // that editor.posting_list() is valid because it wouldn't have successfully
117 // returned by AllocatePostingList if it wasn't. We know
118 // in_memory_posting_list_ is valid because we created it in-memory. And
119 // finally, we know that the data from in_memory_posting_list_ will fit in
120 // editor.posting_list() because we requested it be at at least
121 // posting_list_bytes large.
122 auto status = GetSerializer()->MoveFrom(/*dst=*/&holder.posting_list,
123 /*src=*/&in_memory_posting_list_);
124 if (!status.ok()) {
125 return FinalizeResult(std::move(status), prev_block_identifier_);
126 }
127
128 status = storage_->WritePostingListToDisk(holder);
129 if (!status.ok()) {
130 return FinalizeResult(std::move(status), prev_block_identifier_);
131 }
132 return FinalizeResult(libtextclassifier3::Status::OK, holder.id);
133 }
134
135 } // namespace lib
136 } // namespace icing
137