1 // Copyright (C) 2023 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_FILE_VERSION_UTIL_H_
16 #define ICING_FILE_VERSION_UTIL_H_
17
18 #include <cstdint>
19 #include <memory>
20 #include <string>
21 #include <string_view>
22
23 #include "icing/text_classifier/lib3/utils/base/status.h"
24 #include "icing/text_classifier/lib3/utils/base/statusor.h"
25 #include "icing/absl_ports/str_cat.h"
26 #include "icing/file/filesystem.h"
27 #include "icing/proto/initialize.pb.h"
28
29 namespace icing {
30 namespace lib {
31
32 namespace version_util {
33
34 // - Version 0: Android T base. Can be identified only by flash index magic.
35 // - Version 1: Android U base and M-2023-08.
36 // - Version 2: M-2023-09, M-2023-11, M-2024-01. Schema is compatible with v1.
37 // (There were no M-2023-10, M-2023-12).
38 // - Version 3: M-2024-02. Schema is compatible with v1 and v2.
39 // - Version 4: Android V base. Schema is compatible with v1, v2 and v3.
40 // - Version 5: M-2025-02. Schema is compatible with v1, v2, v3 and v4.
41 inline static constexpr int32_t kVersion = 5;
42 inline static constexpr int32_t kVersionOne = 1;
43 inline static constexpr int32_t kVersionTwo = 2;
44 inline static constexpr int32_t kVersionThree = 3;
45 inline static constexpr int32_t kVersionFour = 4;
46 inline static constexpr int32_t kVersionFive = 5;
47
48 // Version at which v2 version file is introduced.
49 inline static constexpr int32_t kFirstV2Version = kVersionFour;
50
51 // Version at which the database field is introduced to the schema proto.
52 inline static constexpr int32_t kSchemaDatabaseVersion = kVersionFive;
53
54 inline static constexpr int kVersionZeroFlashIndexMagic = 0x6dfba6ae;
55
56 inline static constexpr std::string_view kVersionFilenameV1 = "version";
57 inline static constexpr std::string_view kVersionFilenameV2 = "version2";
58
59 struct VersionInfo {
60 int32_t version;
61 int32_t max_version;
62
VersionInfoVersionInfo63 explicit VersionInfo(int32_t version_in, int32_t max_version_in)
64 : version(version_in), max_version(max_version_in) {}
65
IsValidVersionInfo66 bool IsValid() const { return version >= 0 && max_version >= 0; }
67
68 bool operator==(const VersionInfo& other) const {
69 return version == other.version && max_version == other.max_version;
70 }
71 } __attribute__((packed));
72 static_assert(sizeof(VersionInfo) == 8, "");
73
74 enum class StateChange {
75 kUndetermined,
76 kCompatible,
77 kRollForward,
78 kRollBack,
79 kUpgrade,
80 kVersionZeroUpgrade,
81 kVersionZeroRollForward,
82 };
83
84 // Contains information about which derived files need to be rebuild.
85 //
86 // These flags only reflect whether each component should be rebuilt, but do not
87 // handle any dependencies. The caller should handle the dependencies by
88 // themselves.
89 // e.g. - qualified id join index depends on document store derived files, but
90 // it's possible to have needs_document_store_derived_files_rebuild =
91 // true and needs_qualified_id_join_index_rebuild = false.
92 // - The caller should know that join index should also be rebuilt in this
93 // case even though needs_qualified_id_join_index_rebuild = false.
94 struct DerivedFilesRebuildResult {
95 bool needs_document_store_derived_files_rebuild = false;
96 bool needs_schema_store_derived_files_rebuild = false;
97 bool needs_term_index_rebuild = false;
98 bool needs_integer_index_rebuild = false;
99 bool needs_qualified_id_join_index_rebuild = false;
100 bool needs_embedding_index_rebuild = false;
101
102 DerivedFilesRebuildResult() = default;
103
DerivedFilesRebuildResultDerivedFilesRebuildResult104 explicit DerivedFilesRebuildResult(
105 bool needs_document_store_derived_files_rebuild_in,
106 bool needs_schema_store_derived_files_rebuild_in,
107 bool needs_term_index_rebuild_in, bool needs_integer_index_rebuild_in,
108 bool needs_qualified_id_join_index_rebuild_in,
109 bool needs_embedding_index_rebuild_in)
110 : needs_document_store_derived_files_rebuild(
111 needs_document_store_derived_files_rebuild_in),
112 needs_schema_store_derived_files_rebuild(
113 needs_schema_store_derived_files_rebuild_in),
114 needs_term_index_rebuild(needs_term_index_rebuild_in),
115 needs_integer_index_rebuild(needs_integer_index_rebuild_in),
116 needs_qualified_id_join_index_rebuild(
117 needs_qualified_id_join_index_rebuild_in),
118 needs_embedding_index_rebuild(needs_embedding_index_rebuild_in) {}
119
IsRebuildNeededDerivedFilesRebuildResult120 bool IsRebuildNeeded() const {
121 return needs_document_store_derived_files_rebuild ||
122 needs_schema_store_derived_files_rebuild ||
123 needs_term_index_rebuild || needs_integer_index_rebuild ||
124 needs_qualified_id_join_index_rebuild ||
125 needs_embedding_index_rebuild;
126 }
127
128 bool operator==(const DerivedFilesRebuildResult& other) const {
129 return needs_document_store_derived_files_rebuild ==
130 other.needs_document_store_derived_files_rebuild &&
131 needs_schema_store_derived_files_rebuild ==
132 other.needs_schema_store_derived_files_rebuild &&
133 needs_term_index_rebuild == other.needs_term_index_rebuild &&
134 needs_integer_index_rebuild == other.needs_integer_index_rebuild &&
135 needs_qualified_id_join_index_rebuild ==
136 other.needs_qualified_id_join_index_rebuild &&
137 needs_embedding_index_rebuild == other.needs_embedding_index_rebuild;
138 }
139
CombineWithOtherRebuildResultOrDerivedFilesRebuildResult140 void CombineWithOtherRebuildResultOr(const DerivedFilesRebuildResult& other) {
141 needs_document_store_derived_files_rebuild |=
142 other.needs_document_store_derived_files_rebuild;
143 needs_schema_store_derived_files_rebuild |=
144 other.needs_schema_store_derived_files_rebuild;
145 needs_term_index_rebuild |= other.needs_term_index_rebuild;
146 needs_integer_index_rebuild |= other.needs_integer_index_rebuild;
147 needs_qualified_id_join_index_rebuild |=
148 other.needs_qualified_id_join_index_rebuild;
149 needs_embedding_index_rebuild |= other.needs_embedding_index_rebuild;
150 }
151 };
152
153 // There are two icing version files:
154 // 1. V1 version file contains version and max_version info of the existing
155 // data.
156 // 2. V2 version file writes the version information using
157 // FileBackedProto<IcingSearchEngineVersionProto>. This contains information
158 // about the version's enabled trunk stable features in addition to the
159 // version numbers written for V1.
160 //
161 // Both version files must be written to maintain backwards compatibility.
MakeVersionFilePath(std::string_view version_file_dir,std::string_view version_file_name)162 inline std::string MakeVersionFilePath(std::string_view version_file_dir,
163 std::string_view version_file_name) {
164 return absl_ports::StrCat(version_file_dir, "/", version_file_name);
165 }
166
167 // Returns a VersionInfo from a given IcingSearchEngineVersionProto.
GetVersionInfoFromProto(const IcingSearchEngineVersionProto & version_proto)168 inline VersionInfo GetVersionInfoFromProto(
169 const IcingSearchEngineVersionProto& version_proto) {
170 return VersionInfo(version_proto.version(), version_proto.max_version());
171 }
172
173 // Reads the IcingSearchEngineVersionProto from the version files of the
174 // existing data.
175 //
176 // This method reads both the v1 and v2 version files, and returns the v1
177 // version numbers in the absence of the v2 version file. If there is a mismatch
178 // between the v1 and v2 version numbers, or if the state is invalid (e.g. flash
179 // index header file is missing), then an invalid VersionInfo is returned.
180 //
181 // RETURNS:
182 // - Existing data's IcingSearchEngineVersionProto on success
183 // - INTERNAL_ERROR on I/O errors
184 libtextclassifier3::StatusOr<IcingSearchEngineVersionProto> ReadVersion(
185 const Filesystem& filesystem, const std::string& version_file_dir,
186 const std::string& index_base_dir);
187
188 // Writes the v1 version file. V1 version file is written for all versions and
189 // contains only Icing's VersionInfo (version number and max_version)
190 //
191 // RETURNS:
192 // - OK on success
193 // - INTERNAL_ERROR on I/O errors
194 libtextclassifier3::Status WriteV1Version(const Filesystem& filesystem,
195 const std::string& version_file_dir,
196 const VersionInfo& version_info);
197
198 // Writes the v2 version file. V2 version file writes the version information
199 // using FileBackedProto<IcingSearchEngineVersionProto>.
200 //
201 // REQUIRES: version_proto.version >= kFirstV2Version. We implement v2 version
202 // checking in kFirstV2Version, so callers will always use a version # greater
203 // than this.
204 //
205 // RETURNS:
206 // - OK on success
207 // - INTERNAL_ERROR on I/O errors
208 libtextclassifier3::Status WriteV2Version(
209 const Filesystem& filesystem, const std::string& version_file_dir,
210 std::unique_ptr<IcingSearchEngineVersionProto> version_proto);
211
212 // Deletes Icing's version files from version_file_dir.
213 //
214 // Returns:
215 // - OK on success
216 // - INTERNAL_ERROR on I/O error
217 libtextclassifier3::Status DiscardVersionFiles(
218 const Filesystem& filesystem, std::string_view version_file_dir);
219
220 // Determines the change state between the existing data version and the current
221 // code version.
222 //
223 // REQUIRES: curr_version > 0. We implement version checking in version 1, so
224 // the callers (except unit tests) will always use a version # greater than 0.
225 //
226 // RETURNS: StateChange
227 StateChange GetVersionStateChange(const VersionInfo& existing_version_info,
228 int32_t curr_version = kVersion);
229
230 // Determines the derived files that need to be rebuilt between Icing's existing
231 // data based on previous data version and enabled features, and the current
232 // code version and enabled features.
233 //
234 // REQUIRES: curr_version >= kFirstV2Version. We implement v2 version checking
235 // in kFirstV2Version, so callers will always use a version # greater than this.
236 //
237 // RETURNS: DerivedFilesRebuildResult
238 DerivedFilesRebuildResult CalculateRequiredDerivedFilesRebuild(
239 const IcingSearchEngineVersionProto& prev_version_proto,
240 const IcingSearchEngineVersionProto& curr_version_proto);
241
242 // Determines whether Icing should rebuild all derived files.
243 // Sometimes it is not required to rebuild derived files when
244 // roll-forward/upgrading. This function "encodes" upgrade paths and checks if
245 // the roll-forward/upgrading requires derived files to be rebuilt or not.
246 //
247 // REQUIRES: curr_version > 0. We implement version checking in version 1, so
248 // the callers (except unit tests) will always use a version # greater than 0.
249 bool ShouldRebuildDerivedFiles(const VersionInfo& existing_version_info,
250 int32_t curr_version = kVersion);
251
252 // Returns whether the schema database migration is required.
253 //
254 // This is true if the previous version is less than the version at which the
255 // database field is introduced, or if the schema database feature was
256 // not enabled in the previous version.
257 bool SchemaDatabaseMigrationRequired(
258 const IcingSearchEngineVersionProto& prev_version_proto);
259
260 // Returns the derived files rebuilds required for a given feature.
261 DerivedFilesRebuildResult GetFeatureDerivedFilesRebuildResult(
262 IcingSearchEngineFeatureInfoProto::FlaggedFeatureType feature);
263
264 // Constructs the IcingSearchEngineFeatureInfoProto for a given feature.
265 IcingSearchEngineFeatureInfoProto GetFeatureInfoProto(
266 IcingSearchEngineFeatureInfoProto::FlaggedFeatureType feature);
267
268 // Populates the enabled_features field for an IcingSearchEngineFeatureInfoProto
269 // based on icing's initialization flag options.
270 //
271 // All enabled features are converted into an IcingSearchEngineFeatureInfoProto
272 // and returned in IcingSearchEngineVersionProto::enabled_features. A conversion
273 // should be added for each trunk stable feature flag defined in
274 // IcingSearchEngineOptions.
275 void AddEnabledFeatures(const IcingSearchEngineOptions& options,
276 IcingSearchEngineVersionProto* version_proto);
277
278 } // namespace version_util
279
280 } // namespace lib
281 } // namespace icing
282
283 #endif // ICING_FILE_VERSION_UTIL_H_
284