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