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 #include "icing/schema/schema-store.h"
16
17 #include <cstdint>
18 #include <memory>
19 #include <optional>
20 #include <string>
21 #include <string_view>
22 #include <utility>
23 #include <vector>
24
25 #include "icing/text_classifier/lib3/utils/base/status.h"
26 #include "icing/text_classifier/lib3/utils/base/statusor.h"
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "icing/absl_ports/str_cat.h"
30 #include "icing/document-builder.h"
31 #include "icing/feature-flags.h"
32 #include "icing/file/file-backed-proto.h"
33 #include "icing/file/filesystem.h"
34 #include "icing/file/mock-filesystem.h"
35 #include "icing/file/version-util.h"
36 #include "icing/portable/equals-proto.h"
37 #include "icing/proto/debug.pb.h"
38 #include "icing/proto/document.pb.h"
39 #include "icing/proto/logging.pb.h"
40 #include "icing/proto/schema.pb.h"
41 #include "icing/proto/storage.pb.h"
42 #include "icing/schema-builder.h"
43 #include "icing/schema/section.h"
44 #include "icing/store/document-filter-data.h"
45 #include "icing/testing/common-matchers.h"
46 #include "icing/testing/fake-clock.h"
47 #include "icing/testing/test-feature-flags.h"
48 #include "icing/testing/tmp-directory.h"
49 #include "icing/util/crc32.h"
50
51 namespace icing {
52 namespace lib {
53
54 namespace {
55
56 using ::icing::lib::portable_equals_proto::EqualsProto;
57 using ::testing::ElementsAre;
58 using ::testing::Eq;
59 using ::testing::Ge;
60 using ::testing::Gt;
61 using ::testing::HasSubstr;
62 using ::testing::IsEmpty;
63 using ::testing::Not;
64 using ::testing::Pair;
65 using ::testing::Pointee;
66 using ::testing::Return;
67 using ::testing::SizeIs;
68 using ::testing::UnorderedElementsAre;
69
70 constexpr int64_t kDefaultTimestamp = 12345678;
71
72 class SchemaStoreTest : public ::testing::Test {
73 protected:
SetUp()74 void SetUp() override {
75 feature_flags_ = std::make_unique<FeatureFlags>(GetTestFeatureFlags());
76 test_dir_ = GetTestTempDir() + "/icing";
77 schema_store_dir_ = test_dir_ + "/schema_store";
78 filesystem_.CreateDirectoryRecursively(schema_store_dir_.c_str());
79
80 schema_ = SchemaBuilder()
81 .AddType(SchemaTypeConfigBuilder()
82 .SetType("email")
83 .AddProperty(
84 // Add an indexed property so we generate
85 // section metadata on it
86 PropertyConfigBuilder()
87 .SetName("subject")
88 .SetDataTypeString(TERM_MATCH_EXACT,
89 TOKENIZER_PLAIN)
90 .SetCardinality(CARDINALITY_OPTIONAL))
91 .AddProperty(
92 PropertyConfigBuilder()
93 .SetName("timestamp")
94 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
95 .SetCardinality(CARDINALITY_OPTIONAL)
96 .SetScorableType(SCORABLE_TYPE_ENABLED)))
97 .Build();
98 }
99
TearDown()100 void TearDown() override {
101 // Check that the schema store directory is the *only* directory in the
102 // schema_store_dir_. IOW, ensure that all temporary directories have been
103 // properly cleaned up.
104 std::vector<std::string> sub_dirs;
105 ASSERT_TRUE(filesystem_.ListDirectory(test_dir_.c_str(), &sub_dirs));
106 ASSERT_THAT(sub_dirs, ElementsAre("schema_store"));
107
108 // Finally, clean everything up.
109 ASSERT_TRUE(filesystem_.DeleteDirectoryRecursively(test_dir_.c_str()));
110 }
111
112 std::unique_ptr<FeatureFlags> feature_flags_;
113 Filesystem filesystem_;
114 std::string test_dir_;
115 std::string schema_store_dir_;
116 SchemaProto schema_;
117 FakeClock fake_clock_;
118 };
119
CreateSetSchemaRequestProto(SchemaProto schema,std::string database,bool ignore_errors_and_delete_documents)120 SetSchemaRequestProto CreateSetSchemaRequestProto(
121 SchemaProto schema, std::string database,
122 bool ignore_errors_and_delete_documents) {
123 SetSchemaRequestProto set_schema_request;
124
125 *set_schema_request.mutable_schema() = std::move(schema);
126 set_schema_request.set_database(std::move(database));
127 set_schema_request.set_ignore_errors_and_delete_documents(
128 ignore_errors_and_delete_documents);
129
130 return set_schema_request;
131 }
132
TEST_F(SchemaStoreTest,CreationWithFilesystemNullPointerShouldFail)133 TEST_F(SchemaStoreTest, CreationWithFilesystemNullPointerShouldFail) {
134 EXPECT_THAT(SchemaStore::Create(/*filesystem=*/nullptr, schema_store_dir_,
135 &fake_clock_, feature_flags_.get()),
136 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
137 }
138
TEST_F(SchemaStoreTest,CreationWithClockNullPointerShouldFail)139 TEST_F(SchemaStoreTest, CreationWithClockNullPointerShouldFail) {
140 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_,
141 /*clock=*/nullptr, feature_flags_.get()),
142 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
143 }
144
TEST_F(SchemaStoreTest,CreationWithFeatureFlagsNullPointerShouldFail)145 TEST_F(SchemaStoreTest, CreationWithFeatureFlagsNullPointerShouldFail) {
146 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
147 /*feature_flags=*/nullptr),
148 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
149 }
150
TEST_F(SchemaStoreTest,SchemaStoreMoveConstructible)151 TEST_F(SchemaStoreTest, SchemaStoreMoveConstructible) {
152 // Create an instance of SchemaStore.
153 SchemaProto schema =
154 SchemaBuilder()
155 .AddType(SchemaTypeConfigBuilder().SetType("type_a").AddProperty(
156 PropertyConfigBuilder()
157 .SetName("prop1")
158 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
159 .SetCardinality(CARDINALITY_OPTIONAL)))
160 .Build();
161
162 ICING_ASSERT_OK_AND_ASSIGN(
163 std::unique_ptr<SchemaStore> schema_store,
164 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
165 feature_flags_.get()));
166
167 ICING_ASSERT_OK(schema_store->SetSchema(
168 schema, /*ignore_errors_and_delete_documents=*/false));
169 ICING_ASSERT_OK_AND_ASSIGN(Crc32 expected_checksum,
170 schema_store->UpdateChecksum());
171
172 // Move construct an instance of SchemaStore
173 SchemaStore move_constructed_schema_store(std::move(*schema_store));
174 EXPECT_THAT(move_constructed_schema_store.GetSchema(),
175 IsOkAndHolds(Pointee(EqualsProto(schema))));
176 EXPECT_THAT(move_constructed_schema_store.UpdateChecksum(),
177 IsOkAndHolds(Eq(expected_checksum)));
178 SectionMetadata expected_metadata(/*id_in=*/0, TYPE_STRING, TOKENIZER_PLAIN,
179 TERM_MATCH_EXACT, NUMERIC_MATCH_UNKNOWN,
180 EMBEDDING_INDEXING_UNKNOWN,
181 QUANTIZATION_TYPE_NONE, "prop1");
182 EXPECT_THAT(move_constructed_schema_store.GetSectionMetadata("type_a"),
183 IsOkAndHolds(Pointee(ElementsAre(expected_metadata))));
184 }
185
TEST_F(SchemaStoreTest,SchemaStoreMoveAssignment)186 TEST_F(SchemaStoreTest, SchemaStoreMoveAssignment) {
187 // Create an instance of SchemaStore.
188 SchemaProto schema1 =
189 SchemaBuilder()
190 .AddType(SchemaTypeConfigBuilder().SetType("type_a").AddProperty(
191 PropertyConfigBuilder()
192 .SetName("prop1")
193 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
194 .SetCardinality(CARDINALITY_OPTIONAL)))
195 .Build();
196
197 ICING_ASSERT_OK_AND_ASSIGN(
198 std::unique_ptr<SchemaStore> schema_store,
199 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
200 feature_flags_.get()));
201
202 ICING_ASSERT_OK(schema_store->SetSchema(
203 schema1, /*ignore_errors_and_delete_documents=*/false));
204 ICING_ASSERT_OK_AND_ASSIGN(Crc32 expected_checksum,
205 schema_store->UpdateChecksum());
206
207 // Construct another instance of SchemaStore
208 SchemaProto schema2 =
209 SchemaBuilder()
210 .AddType(SchemaTypeConfigBuilder().SetType("type_b").AddProperty(
211 PropertyConfigBuilder()
212 .SetName("prop2")
213 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
214 .SetCardinality(CARDINALITY_OPTIONAL)))
215 .Build();
216
217 ICING_ASSERT_OK_AND_ASSIGN(
218 std::unique_ptr<SchemaStore> move_assigned_schema_store,
219 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
220 feature_flags_.get()));
221 ICING_ASSERT_OK(schema_store->SetSchema(
222 schema2, /*ignore_errors_and_delete_documents=*/false));
223
224 // Move assign the first instance into the second one.
225 *move_assigned_schema_store = std::move(*schema_store);
226 EXPECT_THAT(move_assigned_schema_store->GetSchema(),
227 IsOkAndHolds(Pointee(EqualsProto(schema1))));
228 EXPECT_THAT(move_assigned_schema_store->UpdateChecksum(),
229 IsOkAndHolds(Eq(expected_checksum)));
230 SectionMetadata expected_metadata(/*id_in=*/0, TYPE_STRING, TOKENIZER_PLAIN,
231 TERM_MATCH_EXACT, NUMERIC_MATCH_UNKNOWN,
232 EMBEDDING_INDEXING_UNKNOWN,
233 QUANTIZATION_TYPE_NONE, "prop1");
234 EXPECT_THAT(move_assigned_schema_store->GetSectionMetadata("type_a"),
235 IsOkAndHolds(Pointee(ElementsAre(expected_metadata))));
236 }
237
TEST_F(SchemaStoreTest,CorruptSchemaError)238 TEST_F(SchemaStoreTest, CorruptSchemaError) {
239 {
240 ICING_ASSERT_OK_AND_ASSIGN(
241 std::unique_ptr<SchemaStore> schema_store,
242 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
243 feature_flags_.get()));
244
245 // Set it for the first time
246 SchemaStore::SetSchemaResult result;
247 result.success = true;
248 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
249 EXPECT_THAT(schema_store->SetSchema(
250 schema_, /*ignore_errors_and_delete_documents=*/false),
251 IsOkAndHolds(EqualsSetSchemaResult(result)));
252 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
253 schema_store->GetSchema());
254 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
255 }
256
257 // "Corrupt" the ground truth schema by adding new data to it. This will mess
258 // up the checksum of the schema store
259
260 SchemaProto corrupt_schema =
261 SchemaBuilder()
262 .AddType(SchemaTypeConfigBuilder().SetType("corrupted"))
263 .Build();
264
265 const std::string schema_file =
266 absl_ports::StrCat(schema_store_dir_, "/schema.pb");
267 const std::string serialized_schema = corrupt_schema.SerializeAsString();
268
269 filesystem_.Write(schema_file.c_str(), serialized_schema.data(),
270 serialized_schema.size());
271
272 // If ground truth was corrupted, we won't know what to do
273 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
274 feature_flags_.get()),
275 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
276 }
277
TEST_F(SchemaStoreTest,RecoverCorruptDerivedFileOk)278 TEST_F(SchemaStoreTest, RecoverCorruptDerivedFileOk) {
279 {
280 ICING_ASSERT_OK_AND_ASSIGN(
281 std::unique_ptr<SchemaStore> schema_store,
282 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
283 feature_flags_.get()));
284
285 // Set it for the first time
286 SchemaStore::SetSchemaResult result;
287 result.success = true;
288 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
289 EXPECT_THAT(schema_store->SetSchema(
290 schema_, /*ignore_errors_and_delete_documents=*/false),
291 IsOkAndHolds(EqualsSetSchemaResult(result)));
292 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
293 schema_store->GetSchema());
294 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
295
296 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
297
298 // Scorable property manager working as expected.
299 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
300 /*schema_type_id=*/0),
301 IsOkAndHolds(Pointee(ElementsAre(
302 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
303 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
304 /*schema_type_id=*/0,
305 /*property_path=*/"timestamp"),
306 IsOkAndHolds(0));
307 }
308
309 // "Corrupt" the derived SchemaTypeIds by deleting the entire directory. This
310 // will mess up the initialization of schema store, causing everything to be
311 // regenerated from ground truth
312
313 const std::string schema_type_mapper_dir =
314 absl_ports::StrCat(schema_store_dir_, "/schema_type_mapper");
315 filesystem_.DeleteDirectoryRecursively(schema_type_mapper_dir.c_str());
316
317 InitializeStatsProto initialize_stats;
318 fake_clock_.SetTimerElapsedMilliseconds(123);
319 ICING_ASSERT_OK_AND_ASSIGN(
320 std::unique_ptr<SchemaStore> schema_store,
321 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
322 feature_flags_.get(), &initialize_stats));
323 EXPECT_THAT(initialize_stats.schema_store_recovery_cause(),
324 Eq(InitializeStatsProto::IO_ERROR));
325 EXPECT_THAT(initialize_stats.schema_store_recovery_latency_ms(), Eq(123));
326
327 // Everything looks fine, ground truth and derived data
328 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
329 schema_store->GetSchema());
330 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
331 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
332
333 // Scorable property manager working as expected.
334 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
335 /*schema_type_id=*/0),
336 IsOkAndHolds(Pointee(ElementsAre(
337 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
338 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
339 /*schema_type_id=*/0,
340 /*property_path=*/"timestamp"),
341 IsOkAndHolds(0));
342 }
343
TEST_F(SchemaStoreTest,RecoverDiscardDerivedFilesOk)344 TEST_F(SchemaStoreTest, RecoverDiscardDerivedFilesOk) {
345 {
346 ICING_ASSERT_OK_AND_ASSIGN(
347 std::unique_ptr<SchemaStore> schema_store,
348 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
349 feature_flags_.get()));
350
351 // Set it for the first time
352 SchemaStore::SetSchemaResult result;
353 result.success = true;
354 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
355 EXPECT_THAT(schema_store->SetSchema(
356 schema_, /*ignore_errors_and_delete_documents=*/false),
357 IsOkAndHolds(EqualsSetSchemaResult(result)));
358 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
359 schema_store->GetSchema());
360 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
361
362 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
363
364 // Scorable property manager working as expected.
365 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
366 /*schema_type_id=*/0),
367 IsOkAndHolds(Pointee(ElementsAre(
368 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
369 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
370 /*schema_type_id=*/0,
371 /*property_path=*/"timestamp"),
372 IsOkAndHolds(0));
373 }
374
375 ICING_ASSERT_OK(
376 SchemaStore::DiscardDerivedFiles(&filesystem_, schema_store_dir_));
377
378 InitializeStatsProto initialize_stats;
379 fake_clock_.SetTimerElapsedMilliseconds(123);
380 ICING_ASSERT_OK_AND_ASSIGN(
381 std::unique_ptr<SchemaStore> schema_store,
382 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
383 feature_flags_.get(), &initialize_stats));
384 EXPECT_THAT(initialize_stats.schema_store_recovery_cause(),
385 Eq(InitializeStatsProto::IO_ERROR));
386 EXPECT_THAT(initialize_stats.schema_store_recovery_latency_ms(), Eq(123));
387
388 // Everything looks fine, ground truth and derived data
389 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
390 schema_store->GetSchema());
391 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
392 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
393
394 // Scorable property manager working as expected.
395 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
396 /*schema_type_id=*/0),
397 IsOkAndHolds(Pointee(ElementsAre(
398 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
399 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
400 /*schema_type_id=*/0,
401 /*property_path=*/"timestamp"),
402 IsOkAndHolds(0));
403 }
404
TEST_F(SchemaStoreTest,RecoverBadChecksumOk)405 TEST_F(SchemaStoreTest, RecoverBadChecksumOk) {
406 {
407 ICING_ASSERT_OK_AND_ASSIGN(
408 std::unique_ptr<SchemaStore> schema_store,
409 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
410 feature_flags_.get()));
411
412 // Set it for the first time
413 SchemaStore::SetSchemaResult result;
414 result.success = true;
415 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
416 EXPECT_THAT(schema_store->SetSchema(
417 schema_, /*ignore_errors_and_delete_documents=*/false),
418 IsOkAndHolds(EqualsSetSchemaResult(result)));
419 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
420 schema_store->GetSchema());
421 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
422
423 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
424
425 // Scorable property manager working as expected.
426 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
427 /*schema_type_id=*/0),
428 IsOkAndHolds(Pointee(ElementsAre(
429 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
430 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
431 /*schema_type_id=*/0,
432 /*property_path=*/"timestamp"),
433 IsOkAndHolds(0));
434 }
435
436 // Change the SchemaStore's header combined checksum so that it won't match
437 // the recalculated checksum on initialization. This will force a regeneration
438 // of derived files from ground truth.
439 const std::string header_file =
440 absl_ports::StrCat(schema_store_dir_, "/schema_store_header");
441 SchemaStore::LegacyHeader header;
442 header.magic = SchemaStore::Header::kMagic;
443 header.checksum = 10; // Arbitrary garbage checksum
444 filesystem_.DeleteFile(header_file.c_str());
445 filesystem_.Write(header_file.c_str(), &header, sizeof(header));
446
447 ICING_ASSERT_OK_AND_ASSIGN(
448 std::unique_ptr<SchemaStore> schema_store,
449 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
450 feature_flags_.get()));
451
452 // Everything looks fine, ground truth and derived data
453 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
454 schema_store->GetSchema());
455 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
456 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
457
458 // Scorable property manager working as expected.
459 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
460 /*schema_type_id=*/0),
461 IsOkAndHolds(Pointee(ElementsAre(
462 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
463 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
464 /*schema_type_id=*/0,
465 /*property_path=*/"timestamp"),
466 IsOkAndHolds(0));
467 }
468
TEST_F(SchemaStoreTest,CreateNoPreviousSchemaOk)469 TEST_F(SchemaStoreTest, CreateNoPreviousSchemaOk) {
470 ICING_ASSERT_OK_AND_ASSIGN(
471 std::unique_ptr<SchemaStore> store,
472 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
473 feature_flags_.get()));
474
475 // The apis to retrieve information about the schema should fail gracefully.
476 EXPECT_THAT(store->GetSchema(),
477 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND));
478 EXPECT_THAT(store->GetSchemaTypeConfig("foo"),
479 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
480 EXPECT_THAT(store->GetSchemaTypeId("foo"),
481 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
482 EXPECT_THAT(store->GetSectionMetadata(/*schema_type_id=*/0, /*section_id=*/0),
483 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
484 EXPECT_THAT(store->GetJoinablePropertyMetadata(/*schema_type_id=*/0,
485 /*property_path=*/"A"),
486 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
487
488 // The apis to extract content from a document should fail gracefully.
489 DocumentProto doc;
490 PropertyProto* prop = doc.add_properties();
491 prop->set_name("name");
492 prop->add_string_values("foo bar baz");
493
494 EXPECT_THAT(store->ExtractSections(doc),
495 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
496 EXPECT_THAT(store->ExtractJoinableProperties(doc),
497 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
498
499 // The apis to persist and checksum data should succeed.
500 EXPECT_THAT(store->UpdateChecksum(), IsOkAndHolds(Crc32()));
501 EXPECT_THAT(store->PersistToDisk(), IsOk());
502 }
503
TEST_F(SchemaStoreTest,CreateWithPreviousSchemaOk)504 TEST_F(SchemaStoreTest, CreateWithPreviousSchemaOk) {
505 ICING_ASSERT_OK_AND_ASSIGN(
506 std::unique_ptr<SchemaStore> schema_store,
507 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
508 feature_flags_.get()));
509
510 SchemaStore::SetSchemaResult result;
511 result.success = true;
512 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
513 EXPECT_THAT(schema_store->SetSchema(
514 schema_, /*ignore_errors_and_delete_documents=*/false),
515 IsOkAndHolds(EqualsSetSchemaResult(result)));
516
517 schema_store.reset();
518 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
519 feature_flags_.get()),
520 IsOk());
521 }
522
TEST_F(SchemaStoreTest,MultipleCreateOk)523 TEST_F(SchemaStoreTest, MultipleCreateOk) {
524 DocumentProto document;
525 document.set_schema("email");
526 auto subject_property = document.add_properties();
527 subject_property->set_name("subject");
528 subject_property->add_string_values("subject_content");
529 auto timestamp_property = document.add_properties();
530 timestamp_property->set_name("timestamp");
531 timestamp_property->add_int64_values(kDefaultTimestamp);
532
533 ICING_ASSERT_OK_AND_ASSIGN(
534 std::unique_ptr<SchemaStore> schema_store,
535 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
536 feature_flags_.get()));
537
538 SchemaStore::SetSchemaResult result;
539 result.success = true;
540 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
541 EXPECT_THAT(schema_store->SetSchema(
542 schema_, /*ignore_errors_and_delete_documents=*/false),
543 IsOkAndHolds(EqualsSetSchemaResult(result)));
544
545 // Verify that our in-memory structures are ok
546 EXPECT_THAT(schema_store->GetSchemaTypeConfig("email"),
547 IsOkAndHolds(Pointee(EqualsProto(schema_.types(0)))));
548 ICING_ASSERT_OK_AND_ASSIGN(SectionGroup section_group,
549 schema_store->ExtractSections(document));
550 EXPECT_THAT(section_group.string_sections[0].content,
551 ElementsAre("subject_content"));
552 EXPECT_THAT(section_group.integer_sections[0].content,
553 ElementsAre(kDefaultTimestamp));
554
555 // Scorable property manager working as expected.
556 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
557 /*schema_type_id=*/0),
558 IsOkAndHolds(Pointee(ElementsAre(
559 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
560 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
561 /*schema_type_id=*/0,
562 /*property_path=*/"timestamp"),
563 IsOkAndHolds(0));
564
565 // Verify that our persisted data is ok
566 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
567
568 schema_store.reset();
569 ICING_ASSERT_OK_AND_ASSIGN(
570 schema_store, SchemaStore::Create(&filesystem_, schema_store_dir_,
571 &fake_clock_, feature_flags_.get()));
572
573 // Verify that our in-memory structures are ok
574 EXPECT_THAT(schema_store->GetSchemaTypeConfig("email"),
575 IsOkAndHolds(Pointee(EqualsProto(schema_.types(0)))));
576
577 ICING_ASSERT_OK_AND_ASSIGN(section_group,
578 schema_store->ExtractSections(document));
579 EXPECT_THAT(section_group.string_sections[0].content,
580 ElementsAre("subject_content"));
581 EXPECT_THAT(section_group.integer_sections[0].content,
582 ElementsAre(kDefaultTimestamp));
583
584 // Scorable property manager working as expected.
585 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
586 /*schema_type_id=*/0),
587 IsOkAndHolds(Pointee(ElementsAre(
588 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
589 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
590 /*schema_type_id=*/0,
591 /*property_path=*/"timestamp"),
592 IsOkAndHolds(0));
593
594 // Verify that our persisted data is ok
595 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
596 }
597
TEST_F(SchemaStoreTest,SetNewSchemaOk)598 TEST_F(SchemaStoreTest, SetNewSchemaOk) {
599 ICING_ASSERT_OK_AND_ASSIGN(
600 std::unique_ptr<SchemaStore> schema_store,
601 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
602 feature_flags_.get()));
603
604 // Set it for the first time
605 SchemaStore::SetSchemaResult result;
606 result.success = true;
607 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
608 EXPECT_THAT(schema_store->SetSchema(
609 schema_, /*ignore_errors_and_delete_documents=*/false),
610 IsOkAndHolds(EqualsSetSchemaResult(result)));
611 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
612 schema_store->GetSchema());
613 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
614 }
615
TEST_F(SchemaStoreTest,SetNewSchemaInDifferentDatabaseOk)616 TEST_F(SchemaStoreTest, SetNewSchemaInDifferentDatabaseOk) {
617 ICING_ASSERT_OK_AND_ASSIGN(
618 std::unique_ptr<SchemaStore> schema_store,
619 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
620 feature_flags_.get(),
621 /*initialize_stats=*/nullptr));
622
623 SchemaProto db1_schema =
624 SchemaBuilder()
625 .AddType(
626 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
627 .AddType(SchemaTypeConfigBuilder()
628 .SetType("db1_message")
629 .SetDatabase("db1"))
630 .Build();
631 SchemaStore::SetSchemaResult result;
632 result.success = true;
633 result.schema_types_new_by_name.insert("db1_email");
634 result.schema_types_new_by_name.insert("db1_message");
635 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
636 db1_schema, /*database=*/"db1",
637 /*ignore_errors_and_delete_documents=*/false)),
638 IsOkAndHolds(EqualsSetSchemaResult(result)));
639 EXPECT_THAT(schema_store->GetSchema(),
640 IsOkAndHolds(Pointee(EqualsProto(db1_schema))));
641 EXPECT_THAT(schema_store->GetSchema("db1"),
642 IsOkAndHolds(EqualsProto(db1_schema)));
643
644 // Set a schema in a different database
645 SchemaProto db2_schema =
646 SchemaBuilder()
647 .AddType(
648 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
649 .AddType(SchemaTypeConfigBuilder()
650 .SetType("db2_message")
651 .SetDatabase("db2"))
652 .Build();
653 result = SchemaStore::SetSchemaResult();
654 result.success = true;
655 result.schema_types_new_by_name.insert("db2_email");
656 result.schema_types_new_by_name.insert("db2_message");
657 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
658 db2_schema, /*database=*/"db2",
659 /*ignore_errors_and_delete_documents=*/false)),
660 IsOkAndHolds(EqualsSetSchemaResult(result)));
661
662 // Check the full schema. Databases that are updated last are appended to the
663 // schema proto
664 SchemaProto expected_full_schema =
665 SchemaBuilder()
666 .AddType(
667 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
668 .AddType(SchemaTypeConfigBuilder()
669 .SetType("db1_message")
670 .SetDatabase("db1"))
671 .AddType(
672 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
673 .AddType(SchemaTypeConfigBuilder()
674 .SetType("db2_message")
675 .SetDatabase("db2"))
676 .Build();
677 EXPECT_THAT(schema_store->GetSchema(),
678 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
679 EXPECT_THAT(schema_store->GetSchema("db1"),
680 IsOkAndHolds(EqualsProto(db1_schema)));
681 EXPECT_THAT(schema_store->GetSchema("db2"),
682 IsOkAndHolds(EqualsProto(db2_schema)));
683 }
684
TEST_F(SchemaStoreTest,SetEmptyDatabaseSchemaOk)685 TEST_F(SchemaStoreTest, SetEmptyDatabaseSchemaOk) {
686 ICING_ASSERT_OK_AND_ASSIGN(
687 std::unique_ptr<SchemaStore> schema_store,
688 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
689 feature_flags_.get()));
690
691 SchemaProto schema =
692 SchemaBuilder()
693 .AddType(SchemaTypeConfigBuilder().SetType("email"))
694 .AddType(SchemaTypeConfigBuilder().SetType("message"))
695 .Build();
696 SchemaStore::SetSchemaResult result;
697 result.success = true;
698 result.schema_types_new_by_name.insert("email");
699 result.schema_types_new_by_name.insert("message");
700 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
701 schema, /*database=*/"",
702 /*ignore_errors_and_delete_documents=*/false)),
703 IsOkAndHolds(EqualsSetSchemaResult(result)));
704 EXPECT_THAT(schema_store->GetSchema(),
705 IsOkAndHolds(Pointee(EqualsProto(schema))));
706 EXPECT_THAT(schema_store->GetSchema(""), IsOkAndHolds(EqualsProto(schema)));
707
708 // Reset the schema. This should still reset the empty schema, and replace
709 // the existing 2 types.
710 schema =
711 SchemaBuilder()
712 .AddType(
713 SchemaTypeConfigBuilder().SetType("email_v2").SetDatabase(""))
714 .AddType(
715 SchemaTypeConfigBuilder().SetType("message_v2").SetDatabase(""))
716 .Build();
717 result = SchemaStore::SetSchemaResult();
718 result.success = true;
719 result.schema_types_new_by_name.insert("email_v2");
720 result.schema_types_new_by_name.insert("message_v2");
721 result.schema_types_deleted_by_name.insert("email");
722 result.schema_types_deleted_by_name.insert("message");
723 result.schema_types_deleted_by_id.insert(0);
724 result.schema_types_deleted_by_id.insert(1);
725
726 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
727 schema, /*database=*/"",
728 /*ignore_errors_and_delete_documents=*/true)),
729 IsOkAndHolds(EqualsSetSchemaResult(result)));
730 EXPECT_THAT(schema_store->GetSchema(),
731 IsOkAndHolds(Pointee(EqualsProto(schema))));
732 EXPECT_THAT(schema_store->GetSchema(""), IsOkAndHolds(EqualsProto(schema)));
733 }
734
TEST_F(SchemaStoreTest,SetSameSchemaOk)735 TEST_F(SchemaStoreTest, SetSameSchemaOk) {
736 ICING_ASSERT_OK_AND_ASSIGN(
737 std::unique_ptr<SchemaStore> schema_store,
738 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
739 feature_flags_.get()));
740
741 // Set it for the first time
742 SchemaStore::SetSchemaResult result;
743 result.success = true;
744 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
745 EXPECT_THAT(schema_store->SetSchema(
746 schema_, /*ignore_errors_and_delete_documents=*/false),
747 IsOkAndHolds(EqualsSetSchemaResult(result)));
748 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
749 schema_store->GetSchema());
750 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
751
752 // And one more for fun
753 result = SchemaStore::SetSchemaResult();
754 result.success = true;
755 EXPECT_THAT(schema_store->SetSchema(
756 schema_, /*ignore_errors_and_delete_documents=*/false),
757 IsOkAndHolds(EqualsSetSchemaResult(result)));
758 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
759 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
760 }
761
TEST_F(SchemaStoreTest,SetSameDatabaseSchemaOk)762 TEST_F(SchemaStoreTest, SetSameDatabaseSchemaOk) {
763 ICING_ASSERT_OK_AND_ASSIGN(
764 std::unique_ptr<SchemaStore> schema_store,
765 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
766 feature_flags_.get(),
767 /*initialize_stats=*/nullptr));
768
769 // Set schema for the first time
770 SchemaProto db1_schema =
771 SchemaBuilder()
772 .AddType(
773 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
774 .AddType(SchemaTypeConfigBuilder()
775 .SetType("db1_message")
776 .SetDatabase("db1"))
777 .Build();
778 SchemaProto db2_schema =
779 SchemaBuilder()
780 .AddType(
781 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
782 .AddType(SchemaTypeConfigBuilder()
783 .SetType("db2_message")
784 .SetDatabase("db2"))
785 .Build();
786 SchemaProto expected_full_schema =
787 SchemaBuilder()
788 .AddType(
789 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
790 .AddType(SchemaTypeConfigBuilder()
791 .SetType("db1_message")
792 .SetDatabase("db1"))
793 .AddType(
794 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
795 .AddType(SchemaTypeConfigBuilder()
796 .SetType("db2_message")
797 .SetDatabase("db2"))
798 .Build();
799 SchemaStore::SetSchemaResult result;
800 result.success = true;
801 result.schema_types_new_by_name.insert("db1_email");
802 result.schema_types_new_by_name.insert("db1_message");
803 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
804 db1_schema, /*database=*/"db1",
805 /*ignore_errors_and_delete_documents=*/false)),
806 IsOkAndHolds(EqualsSetSchemaResult(result)));
807 result = SchemaStore::SetSchemaResult();
808 result.success = true;
809 result.schema_types_new_by_name.insert("db2_email");
810 result.schema_types_new_by_name.insert("db2_message");
811 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
812 db2_schema, /*database=*/"db2",
813 /*ignore_errors_and_delete_documents=*/false)),
814 IsOkAndHolds(EqualsSetSchemaResult(result)));
815 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_full_schema,
816 schema_store->GetSchema());
817 EXPECT_THAT(*actual_full_schema, EqualsProto(expected_full_schema));
818
819 // Reset db1 with the same SchemaProto. The schema should be exactly the same.
820 result = SchemaStore::SetSchemaResult();
821 result.success = true;
822 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
823 db1_schema, /*database=*/"db1",
824 /*ignore_errors_and_delete_documents=*/false)),
825 IsOkAndHolds(EqualsSetSchemaResult(result)));
826
827 // Check the schema, this should not have changed
828 EXPECT_THAT(schema_store->GetSchema(),
829 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
830 EXPECT_THAT(schema_store->GetSchema("db1"),
831 IsOkAndHolds(EqualsProto(db1_schema)));
832 EXPECT_THAT(schema_store->GetSchema("db2"),
833 IsOkAndHolds(EqualsProto(db2_schema)));
834 }
835
TEST_F(SchemaStoreTest,SetDatabaseReorderedTypesNoChange)836 TEST_F(SchemaStoreTest, SetDatabaseReorderedTypesNoChange) {
837 ICING_ASSERT_OK_AND_ASSIGN(
838 std::unique_ptr<SchemaStore> schema_store,
839 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
840 feature_flags_.get(),
841 /*initialize_stats=*/nullptr));
842
843 // Set schema for the first time
844 SchemaProto db1_schema =
845 SchemaBuilder()
846 .AddType(
847 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
848 .AddType(SchemaTypeConfigBuilder()
849 .SetType("db1_message")
850 .SetDatabase("db1"))
851 .Build();
852 SchemaProto db2_schema =
853 SchemaBuilder()
854 .AddType(
855 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
856 .AddType(SchemaTypeConfigBuilder()
857 .SetType("db2_message")
858 .SetDatabase("db2"))
859 .Build();
860 SchemaProto db3_schema =
861 SchemaBuilder()
862 .AddType(
863 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
864 .AddType(SchemaTypeConfigBuilder()
865 .SetType("db3_message")
866 .SetDatabase("db3"))
867 .Build();
868 SchemaProto expected_full_schema =
869 SchemaBuilder()
870 .AddType(
871 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
872 .AddType(SchemaTypeConfigBuilder()
873 .SetType("db1_message")
874 .SetDatabase("db1"))
875 .AddType(
876 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
877 .AddType(SchemaTypeConfigBuilder()
878 .SetType("db2_message")
879 .SetDatabase("db2"))
880 .AddType(
881 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
882 .AddType(SchemaTypeConfigBuilder()
883 .SetType("db3_message")
884 .SetDatabase("db3"))
885 .Build();
886
887 // Set schema for db1
888 SchemaStore::SetSchemaResult result;
889 result.success = true;
890 result.schema_types_new_by_name.insert("db1_email");
891 result.schema_types_new_by_name.insert("db1_message");
892 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
893 db1_schema, /*database=*/"db1",
894 /*ignore_errors_and_delete_documents=*/false)),
895 IsOkAndHolds(EqualsSetSchemaResult(result)));
896 // Set schema for db2
897 result = SchemaStore::SetSchemaResult();
898 result.success = true;
899 result.schema_types_new_by_name.insert("db2_email");
900 result.schema_types_new_by_name.insert("db2_message");
901 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
902 db2_schema, /*database=*/"db2",
903 /*ignore_errors_and_delete_documents=*/false)),
904 IsOkAndHolds(EqualsSetSchemaResult(result)));
905 // Set schema for db3
906 result = SchemaStore::SetSchemaResult();
907 result.success = true;
908 result.schema_types_new_by_name.insert("db3_email");
909 result.schema_types_new_by_name.insert("db3_message");
910 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
911 db3_schema, /*database=*/"db3",
912 /*ignore_errors_and_delete_documents=*/false)),
913 IsOkAndHolds(EqualsSetSchemaResult(result)));
914 // Verify schema.
915 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_full_schema,
916 schema_store->GetSchema());
917 EXPECT_THAT(*actual_full_schema, EqualsProto(expected_full_schema));
918
919 // Reset db2 with the types reordered. This should not change the existing
920 // schema in any way.
921 SchemaProto reordered_db2_schema =
922 SchemaBuilder()
923 .AddType(SchemaTypeConfigBuilder()
924 .SetType("db2_message")
925 .SetDatabase("db2"))
926 .AddType(
927 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
928 .Build();
929 result = SchemaStore::SetSchemaResult();
930 result.success = true;
931
932 libtextclassifier3::StatusOr<SchemaStore::SetSchemaResult> actual_result =
933 schema_store->SetSchema(CreateSetSchemaRequestProto(
934 reordered_db2_schema, /*database=*/"db2",
935 /*ignore_errors_and_delete_documents=*/false));
936 EXPECT_THAT(actual_result, IsOkAndHolds(EqualsSetSchemaResult(result)));
937 EXPECT_THAT(actual_result.ValueOrDie().old_schema_type_ids_changed,
938 IsEmpty());
939
940 // Check the schema
941 EXPECT_THAT(schema_store->GetSchema(),
942 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
943 EXPECT_THAT(schema_store->GetSchema("db1"),
944 IsOkAndHolds(EqualsProto(db1_schema)));
945
946 libtextclassifier3::StatusOr<SchemaProto> actual_db2_schema =
947 schema_store->GetSchema("db2");
948 EXPECT_THAT(actual_db2_schema, IsOkAndHolds(EqualsProto(db2_schema)));
949 EXPECT_THAT(actual_db2_schema.ValueOrDie(),
950 Not(EqualsProto(reordered_db2_schema)));
951
952 EXPECT_THAT(schema_store->GetSchema("db3"),
953 IsOkAndHolds(EqualsProto(db3_schema)));
954 }
955
TEST_F(SchemaStoreTest,SetDatabaseAddedTypesPreservesSchemaTypeIds)956 TEST_F(SchemaStoreTest, SetDatabaseAddedTypesPreservesSchemaTypeIds) {
957 ICING_ASSERT_OK_AND_ASSIGN(
958 std::unique_ptr<SchemaStore> schema_store,
959 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
960 feature_flags_.get(),
961 /*initialize_stats=*/nullptr));
962
963 // Set schema for the first time
964 SchemaProto db1_schema =
965 SchemaBuilder()
966 .AddType(
967 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
968 .AddType(SchemaTypeConfigBuilder()
969 .SetType("db1_message")
970 .SetDatabase("db1"))
971 .Build();
972 SchemaProto db2_schema =
973 SchemaBuilder()
974 .AddType(
975 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
976 .AddType(SchemaTypeConfigBuilder()
977 .SetType("db2_message")
978 .SetDatabase("db2"))
979 .Build();
980 SchemaProto db3_schema =
981 SchemaBuilder()
982 .AddType(
983 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
984 .AddType(SchemaTypeConfigBuilder()
985 .SetType("db3_message")
986 .SetDatabase("db3"))
987 .Build();
988 SchemaProto expected_full_schema =
989 SchemaBuilder()
990 .AddType(
991 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
992 .AddType(SchemaTypeConfigBuilder()
993 .SetType("db1_message")
994 .SetDatabase("db1"))
995 .AddType(
996 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
997 .AddType(SchemaTypeConfigBuilder()
998 .SetType("db2_message")
999 .SetDatabase("db2"))
1000 .AddType(
1001 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
1002 .AddType(SchemaTypeConfigBuilder()
1003 .SetType("db3_message")
1004 .SetDatabase("db3"))
1005 .Build();
1006
1007 // Set schema for db1
1008 SchemaStore::SetSchemaResult result;
1009 result.success = true;
1010 result.schema_types_new_by_name.insert("db1_email");
1011 result.schema_types_new_by_name.insert("db1_message");
1012 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1013 db1_schema, /*database=*/"db1",
1014 /*ignore_errors_and_delete_documents=*/false)),
1015 IsOkAndHolds(EqualsSetSchemaResult(result)));
1016 // Set schema for db2
1017 result = SchemaStore::SetSchemaResult();
1018 result.success = true;
1019 result.schema_types_new_by_name.insert("db2_email");
1020 result.schema_types_new_by_name.insert("db2_message");
1021 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1022 db2_schema, /*database=*/"db2",
1023 /*ignore_errors_and_delete_documents=*/false)),
1024 IsOkAndHolds(EqualsSetSchemaResult(result)));
1025 // Set schema for db3
1026 result = SchemaStore::SetSchemaResult();
1027 result.success = true;
1028 result.schema_types_new_by_name.insert("db3_email");
1029 result.schema_types_new_by_name.insert("db3_message");
1030 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1031 db3_schema, /*database=*/"db3",
1032 /*ignore_errors_and_delete_documents=*/false)),
1033 IsOkAndHolds(EqualsSetSchemaResult(result)));
1034 // Verify schema.
1035 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_full_schema,
1036 schema_store->GetSchema());
1037 EXPECT_THAT(*actual_full_schema, EqualsProto(expected_full_schema));
1038
1039 // Reset db2 and add a type. The added type should be appended to the end of
1040 // the SchemaProto, and SchemaTypeIds for db1 and db3 should not change.
1041 //
1042 // Whether or not the SchemaTypeIds for db2 change depends on the order in the
1043 // new db2 SchemaProto (in this case, existing type's order and ids do not
1044 // change)
1045 db2_schema =
1046 SchemaBuilder()
1047 .AddType(
1048 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1049 .AddType(SchemaTypeConfigBuilder()
1050 .SetType("db2_message")
1051 .SetDatabase("db2"))
1052 .AddType(SchemaTypeConfigBuilder()
1053 .SetType("db2_recipient")
1054 .SetDatabase("db2"))
1055 .Build();
1056 expected_full_schema =
1057 SchemaBuilder()
1058 .AddType(SchemaTypeConfigBuilder() // db1 types
1059 .SetType("db1_email")
1060 .SetDatabase("db1"))
1061 .AddType(SchemaTypeConfigBuilder()
1062 .SetType("db1_message")
1063 .SetDatabase("db1"))
1064 .AddType(SchemaTypeConfigBuilder() // db2 types
1065 .SetType("db2_email")
1066 .SetDatabase("db2"))
1067 .AddType(SchemaTypeConfigBuilder()
1068 .SetType("db2_message")
1069 .SetDatabase("db2"))
1070 .AddType(SchemaTypeConfigBuilder() // db3 types
1071 .SetType("db3_email")
1072 .SetDatabase("db3"))
1073 .AddType(SchemaTypeConfigBuilder()
1074 .SetType("db3_message")
1075 .SetDatabase("db3"))
1076 .AddType(SchemaTypeConfigBuilder() // Additional db2 type is appended
1077 // at the end
1078 .SetType("db2_recipient")
1079 .SetDatabase("db2"))
1080 .Build();
1081 result = SchemaStore::SetSchemaResult();
1082 result.success = true;
1083 result.schema_types_new_by_name.insert("db2_recipient");
1084 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1085 db2_schema, /*database=*/"db2",
1086 /*ignore_errors_and_delete_documents=*/false)),
1087 IsOkAndHolds(EqualsSetSchemaResult(result)));
1088
1089 // Check the schema
1090 EXPECT_THAT(schema_store->GetSchema(),
1091 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
1092 EXPECT_THAT(schema_store->GetSchema("db1"),
1093 IsOkAndHolds(EqualsProto(db1_schema)));
1094 EXPECT_THAT(schema_store->GetSchema("db2"),
1095 IsOkAndHolds(EqualsProto(db2_schema)));
1096 EXPECT_THAT(schema_store->GetSchema("db3"),
1097 IsOkAndHolds(EqualsProto(db3_schema)));
1098 }
1099
TEST_F(SchemaStoreTest,SetDatabaseDeletedTypesOk)1100 TEST_F(SchemaStoreTest, SetDatabaseDeletedTypesOk) {
1101 ICING_ASSERT_OK_AND_ASSIGN(
1102 std::unique_ptr<SchemaStore> schema_store,
1103 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1104 feature_flags_.get(),
1105 /*initialize_stats=*/nullptr));
1106
1107 // Set schema for the first time
1108 SchemaProto db1_schema =
1109 SchemaBuilder()
1110 .AddType(
1111 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1112 .AddType(SchemaTypeConfigBuilder()
1113 .SetType("db1_message")
1114 .SetDatabase("db1"))
1115 .Build();
1116 SchemaProto db2_schema =
1117 SchemaBuilder()
1118 .AddType(
1119 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1120 .AddType(SchemaTypeConfigBuilder()
1121 .SetType("db2_message")
1122 .SetDatabase("db2"))
1123 .Build();
1124 SchemaProto db3_schema =
1125 SchemaBuilder()
1126 .AddType(
1127 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
1128 .AddType(SchemaTypeConfigBuilder()
1129 .SetType("db3_message")
1130 .SetDatabase("db3"))
1131 .Build();
1132
1133 // Set schema for db1
1134 SchemaStore::SetSchemaResult result;
1135 result.success = true;
1136 result.schema_types_new_by_name.insert("db1_email");
1137 result.schema_types_new_by_name.insert("db1_message");
1138 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1139 db1_schema, /*database=*/"db1",
1140 /*ignore_errors_and_delete_documents=*/false)),
1141 IsOkAndHolds(EqualsSetSchemaResult(result)));
1142 // Set schema for db2
1143 result = SchemaStore::SetSchemaResult();
1144 result.success = true;
1145 result.schema_types_new_by_name.insert("db2_email");
1146 result.schema_types_new_by_name.insert("db2_message");
1147 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1148 db2_schema, /*database=*/"db2",
1149 /*ignore_errors_and_delete_documents=*/false)),
1150 IsOkAndHolds(EqualsSetSchemaResult(result)));
1151 // Set schema for db3
1152 result = SchemaStore::SetSchemaResult();
1153 result.success = true;
1154 result.schema_types_new_by_name.insert("db3_email");
1155 result.schema_types_new_by_name.insert("db3_message");
1156 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1157 db3_schema, /*database=*/"db3",
1158 /*ignore_errors_and_delete_documents=*/false)),
1159 IsOkAndHolds(EqualsSetSchemaResult(result)));
1160 // Set schema again for db2 and add a type. The added type should be appended
1161 // to the end of the SchemaProto.
1162 db2_schema =
1163 SchemaBuilder()
1164 .AddType(
1165 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1166 .AddType(SchemaTypeConfigBuilder()
1167 .SetType("db2_message")
1168 .SetDatabase("db2"))
1169 .AddType(SchemaTypeConfigBuilder()
1170 .SetType("db2_recipient")
1171 .SetDatabase("db2"))
1172 .Build();
1173 result = SchemaStore::SetSchemaResult();
1174 result.success = true;
1175 result.schema_types_new_by_name.insert("db2_recipient");
1176 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1177 db2_schema, /*database=*/"db2",
1178 /*ignore_errors_and_delete_documents=*/false)),
1179 IsOkAndHolds(EqualsSetSchemaResult(result)));
1180 SchemaProto expected_full_schema =
1181 SchemaBuilder()
1182 .AddType(SchemaTypeConfigBuilder()
1183 .SetType("db1_email") // SchemaTypeId 0
1184 .SetDatabase("db1"))
1185 .AddType(SchemaTypeConfigBuilder()
1186 .SetType("db1_message") // SchemaTypeId 1
1187 .SetDatabase("db1"))
1188 .AddType(SchemaTypeConfigBuilder()
1189 .SetType("db2_email") // SchemaTypeId 2
1190 .SetDatabase("db2"))
1191 .AddType(SchemaTypeConfigBuilder()
1192 .SetType("db2_message") // SchemaTypeId 3
1193 .SetDatabase("db2"))
1194 .AddType(SchemaTypeConfigBuilder()
1195 .SetType("db3_email") // SchemaTypeId 4
1196 .SetDatabase("db3"))
1197 .AddType(SchemaTypeConfigBuilder()
1198 .SetType("db3_message") // SchemaTypeId 5
1199 .SetDatabase("db3"))
1200 .AddType(SchemaTypeConfigBuilder()
1201 .SetType("db2_recipient") // SchemaTypeId 6
1202 .SetDatabase("db2"))
1203 .Build();
1204 // Verify schema.
1205 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_full_schema,
1206 schema_store->GetSchema());
1207 EXPECT_THAT(*actual_full_schema, EqualsProto(expected_full_schema));
1208
1209 // Reset db2 and delete some types. All types that were originally added after
1210 // db2 should have their type ids changed.
1211 db2_schema = SchemaBuilder()
1212 .AddType(SchemaTypeConfigBuilder()
1213 .SetType("db2_message")
1214 .SetDatabase("db2"))
1215 .Build();
1216 expected_full_schema =
1217 SchemaBuilder()
1218 .AddType(SchemaTypeConfigBuilder()
1219 .SetType("db1_email") // SchemaTypeId 0
1220 .SetDatabase("db1"))
1221 .AddType(SchemaTypeConfigBuilder()
1222 .SetType("db1_message") // SchemaTypeId 1
1223 .SetDatabase("db1"))
1224 .AddType(SchemaTypeConfigBuilder()
1225 .SetType("db2_message") // SchemaTypeId 2
1226 .SetDatabase("db2"))
1227 .AddType(SchemaTypeConfigBuilder()
1228 .SetType("db3_email") // SchemaTypeId 3
1229 .SetDatabase("db3"))
1230 .AddType(SchemaTypeConfigBuilder()
1231 .SetType("db3_message") // SchemaTypeId 4
1232 .SetDatabase("db3"))
1233 .Build();
1234 result = SchemaStore::SetSchemaResult();
1235 result.success = true;
1236 result.schema_types_deleted_by_name.insert("db2_email");
1237 result.schema_types_deleted_by_name.insert("db2_recipient");
1238 result.schema_types_deleted_by_id.insert(2); // db2_email
1239 result.schema_types_deleted_by_id.insert(6); // db2_recipient
1240 result.old_schema_type_ids_changed.insert(3); // db2_message
1241 result.old_schema_type_ids_changed.insert(4); // db3_email
1242 result.old_schema_type_ids_changed.insert(5); // db3_message
1243 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1244 db2_schema, /*database=*/"db2",
1245 /*ignore_errors_and_delete_documents=*/true)),
1246 IsOkAndHolds(EqualsSetSchemaResult(result)));
1247
1248 // Check the schema
1249 EXPECT_THAT(schema_store->GetSchema(),
1250 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
1251 EXPECT_THAT(schema_store->GetSchema("db1"),
1252 IsOkAndHolds(EqualsProto(db1_schema)));
1253 EXPECT_THAT(schema_store->GetSchema("db2"),
1254 IsOkAndHolds(EqualsProto(db2_schema)));
1255 EXPECT_THAT(schema_store->GetSchema("db3"),
1256 IsOkAndHolds(EqualsProto(db3_schema)));
1257 }
1258
TEST_F(SchemaStoreTest,SetEmptySchemaClearsDatabase)1259 TEST_F(SchemaStoreTest, SetEmptySchemaClearsDatabase) {
1260 ICING_ASSERT_OK_AND_ASSIGN(
1261 std::unique_ptr<SchemaStore> schema_store,
1262 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1263 feature_flags_.get(),
1264 /*initialize_stats=*/nullptr));
1265
1266 // Set schema for the first time
1267 SchemaProto db1_schema =
1268 SchemaBuilder()
1269 .AddType(
1270 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1271 .AddType(SchemaTypeConfigBuilder()
1272 .SetType("db1_message")
1273 .SetDatabase("db1"))
1274 .Build();
1275 SchemaProto db2_schema =
1276 SchemaBuilder()
1277 .AddType(
1278 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1279 .AddType(SchemaTypeConfigBuilder()
1280 .SetType("db2_message")
1281 .SetDatabase("db2"))
1282 .Build();
1283 SchemaProto db3_schema =
1284 SchemaBuilder()
1285 .AddType(
1286 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
1287 .AddType(SchemaTypeConfigBuilder()
1288 .SetType("db3_message")
1289 .SetDatabase("db3"))
1290 .Build();
1291
1292 // Set schema for db1
1293 SchemaStore::SetSchemaResult result;
1294 result.success = true;
1295 result.schema_types_new_by_name.insert("db1_email");
1296 result.schema_types_new_by_name.insert("db1_message");
1297 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1298 db1_schema, /*database=*/"db1",
1299 /*ignore_errors_and_delete_documents=*/false)),
1300 IsOkAndHolds(EqualsSetSchemaResult(result)));
1301 // Set schema for db2
1302 result = SchemaStore::SetSchemaResult();
1303 result.success = true;
1304 result.schema_types_new_by_name.insert("db2_email");
1305 result.schema_types_new_by_name.insert("db2_message");
1306 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1307 db2_schema, /*database=*/"db2",
1308 /*ignore_errors_and_delete_documents=*/false)),
1309 IsOkAndHolds(EqualsSetSchemaResult(result)));
1310 // Set schema for db3
1311 result = SchemaStore::SetSchemaResult();
1312 result.success = true;
1313 result.schema_types_new_by_name.insert("db3_email");
1314 result.schema_types_new_by_name.insert("db3_message");
1315 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1316 db3_schema, /*database=*/"db3",
1317 /*ignore_errors_and_delete_documents=*/false)),
1318 IsOkAndHolds(EqualsSetSchemaResult(result)));
1319 // Verify schema.
1320 SchemaProto expected_full_schema =
1321 SchemaBuilder()
1322 .AddType(SchemaTypeConfigBuilder()
1323 .SetType("db1_email") // SchemaTypeId 0
1324 .SetDatabase("db1"))
1325 .AddType(SchemaTypeConfigBuilder()
1326 .SetType("db1_message") // SchemaTypeId 1
1327 .SetDatabase("db1"))
1328 .AddType(SchemaTypeConfigBuilder()
1329 .SetType("db2_email") // SchemaTypeId 2
1330 .SetDatabase("db2"))
1331 .AddType(SchemaTypeConfigBuilder()
1332 .SetType("db2_message") // SchemaTypeId 3
1333 .SetDatabase("db2"))
1334 .AddType(SchemaTypeConfigBuilder()
1335 .SetType("db3_email") // SchemaTypeId 4
1336 .SetDatabase("db3"))
1337 .AddType(SchemaTypeConfigBuilder()
1338 .SetType("db3_message") // SchemaTypeId 5
1339 .SetDatabase("db3"))
1340 .Build();
1341 EXPECT_THAT(schema_store->GetSchema(),
1342 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
1343
1344 // Set an empty schema for db2. This deletes all types from db2, and changes
1345 // the type ids of types from db3 because they appear after db2 in the
1346 // original schema.
1347 db2_schema = SchemaProto();
1348 result = SchemaStore::SetSchemaResult();
1349 result.success = true;
1350 result.schema_types_deleted_by_name.insert("db2_email");
1351 result.schema_types_deleted_by_name.insert("db2_message");
1352 result.schema_types_deleted_by_id.insert(2); // db2_email
1353 result.schema_types_deleted_by_id.insert(3); // db2_message
1354 result.old_schema_type_ids_changed.insert(4); // db3_email
1355 result.old_schema_type_ids_changed.insert(5); // db3_message
1356 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1357 db2_schema, /*database=*/"db2",
1358 /*ignore_errors_and_delete_documents=*/true)),
1359 IsOkAndHolds(EqualsSetSchemaResult(result)));
1360
1361 // Check the schema. Schemas for db1 and db3 should be unchanged.
1362 EXPECT_THAT(schema_store->GetSchema("db1"),
1363 IsOkAndHolds(EqualsProto(db1_schema)));
1364 EXPECT_THAT(schema_store->GetSchema("db3"),
1365 IsOkAndHolds(EqualsProto(db3_schema)));
1366
1367 // GetSchema for db2 should return NotFoundError
1368 EXPECT_THAT(schema_store->GetSchema("db2"),
1369 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND));
1370
1371 expected_full_schema =
1372 SchemaBuilder()
1373 .AddType(
1374 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1375 .AddType(SchemaTypeConfigBuilder()
1376 .SetType("db1_message")
1377 .SetDatabase("db1"))
1378 .AddType(
1379 SchemaTypeConfigBuilder().SetType("db3_email").SetDatabase("db3"))
1380 .AddType(SchemaTypeConfigBuilder()
1381 .SetType("db3_message")
1382 .SetDatabase("db3"))
1383 .Build();
1384 EXPECT_THAT(schema_store->GetSchema(),
1385 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
1386 }
1387
TEST_F(SchemaStoreTest,SetIncompatibleSchemaOk)1388 TEST_F(SchemaStoreTest, SetIncompatibleSchemaOk) {
1389 ICING_ASSERT_OK_AND_ASSIGN(
1390 std::unique_ptr<SchemaStore> schema_store,
1391 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1392 feature_flags_.get()));
1393
1394 // Set it for the first time
1395 SchemaStore::SetSchemaResult result;
1396 result.success = true;
1397 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
1398 EXPECT_THAT(schema_store->SetSchema(
1399 schema_, /*ignore_errors_and_delete_documents=*/false),
1400 IsOkAndHolds(EqualsSetSchemaResult(result)));
1401 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
1402 schema_store->GetSchema());
1403 EXPECT_THAT(*actual_schema, EqualsProto(schema_));
1404
1405 // Make the schema incompatible by removing a type.
1406 schema_.clear_types();
1407
1408 // Set the incompatible schema
1409 result = SchemaStore::SetSchemaResult();
1410 result.success = false;
1411 result.schema_types_deleted_by_name.emplace("email");
1412 result.schema_types_deleted_by_id.emplace(0);
1413 EXPECT_THAT(schema_store->SetSchema(
1414 schema_, /*ignore_errors_and_delete_documents=*/false),
1415 IsOkAndHolds(EqualsSetSchemaResult(result)));
1416 }
1417
TEST_F(SchemaStoreTest,SetIncompatibleInDifferentDatabaseOk)1418 TEST_F(SchemaStoreTest, SetIncompatibleInDifferentDatabaseOk) {
1419 ICING_ASSERT_OK_AND_ASSIGN(
1420 std::unique_ptr<SchemaStore> schema_store,
1421 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1422 feature_flags_.get(),
1423 /*initialize_stats=*/nullptr));
1424
1425 // Set schema for the first time
1426 SchemaProto db1_schema =
1427 SchemaBuilder()
1428 .AddType(
1429 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1430 .AddType(SchemaTypeConfigBuilder()
1431 .SetType("db1_message")
1432 .SetDatabase("db1"))
1433 .Build();
1434 SchemaProto db2_schema =
1435 SchemaBuilder()
1436 .AddType(
1437 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1438 .AddType(SchemaTypeConfigBuilder()
1439 .SetType("db2_message")
1440 .SetDatabase("db2"))
1441 .Build();
1442 SchemaProto expected_full_schema =
1443 SchemaBuilder()
1444 .AddType(
1445 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1446 .AddType(SchemaTypeConfigBuilder()
1447 .SetType("db1_message")
1448 .SetDatabase("db1"))
1449 .AddType(
1450 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1451 .AddType(SchemaTypeConfigBuilder()
1452 .SetType("db2_message")
1453 .SetDatabase("db2"))
1454 .Build();
1455 SchemaStore::SetSchemaResult result;
1456 result.success = true;
1457 result.schema_types_new_by_name.insert("db1_email");
1458 result.schema_types_new_by_name.insert("db1_message");
1459 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1460 db1_schema, /*database=*/"db1",
1461 /*ignore_errors_and_delete_documents=*/false)),
1462 IsOkAndHolds(EqualsSetSchemaResult(result)));
1463 result = SchemaStore::SetSchemaResult();
1464 result.success = true;
1465 result.schema_types_new_by_name.insert("db2_email");
1466 result.schema_types_new_by_name.insert("db2_message");
1467 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1468 db2_schema, /*database=*/"db2",
1469 /*ignore_errors_and_delete_documents=*/false)),
1470 IsOkAndHolds(EqualsSetSchemaResult(result)));
1471 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_full_schema,
1472 schema_store->GetSchema());
1473 EXPECT_THAT(*actual_full_schema, EqualsProto(expected_full_schema));
1474
1475 // Make db2 incompatible by changing a type name
1476 SchemaProto db2_schema_incompatible =
1477 SchemaBuilder()
1478 .AddType(
1479 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1480 .AddType(SchemaTypeConfigBuilder()
1481 .SetType("db2_recipient")
1482 .SetDatabase("db2"))
1483 .Build();
1484 result = SchemaStore::SetSchemaResult();
1485 result.success = false;
1486 result.schema_types_deleted_by_name.insert("db2_message");
1487 result.schema_types_new_by_name.insert("db2_recipient");
1488 result.schema_types_deleted_by_id.insert(3); // db2_message
1489 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1490 db2_schema_incompatible, /*database=*/"db2",
1491 /*ignore_errors_and_delete_documents=*/false)),
1492 IsOkAndHolds(EqualsSetSchemaResult(result)));
1493
1494 // Check the schema, this should not have changed
1495 EXPECT_THAT(schema_store->GetSchema(),
1496 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
1497 EXPECT_THAT(schema_store->GetSchema("db1"),
1498 IsOkAndHolds(EqualsProto(db1_schema)));
1499 EXPECT_THAT(schema_store->GetSchema("db2"),
1500 IsOkAndHolds(EqualsProto(db2_schema)));
1501 }
1502
TEST_F(SchemaStoreTest,SetInvalidInDifferentDatabaseFails)1503 TEST_F(SchemaStoreTest, SetInvalidInDifferentDatabaseFails) {
1504 ICING_ASSERT_OK_AND_ASSIGN(
1505 std::unique_ptr<SchemaStore> schema_store,
1506 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1507 feature_flags_.get(),
1508 /*initialize_stats=*/nullptr));
1509
1510 // Set schema for the first time
1511 SchemaProto db1_schema =
1512 SchemaBuilder()
1513 .AddType(
1514 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1515 .AddType(SchemaTypeConfigBuilder()
1516 .SetType("db1_message")
1517 .SetDatabase("db1"))
1518 .Build();
1519 SchemaProto db2_schema =
1520 SchemaBuilder()
1521 .AddType(
1522 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1523 .AddType(SchemaTypeConfigBuilder()
1524 .SetType("db2_message")
1525 .SetDatabase("db2"))
1526 .Build();
1527 SchemaProto expected_full_schema =
1528 SchemaBuilder()
1529 .AddType(
1530 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1531 .AddType(SchemaTypeConfigBuilder()
1532 .SetType("db1_message")
1533 .SetDatabase("db1"))
1534 .AddType(
1535 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1536 .AddType(SchemaTypeConfigBuilder()
1537 .SetType("db2_message")
1538 .SetDatabase("db2"))
1539 .Build();
1540 SchemaStore::SetSchemaResult result;
1541 result.success = true;
1542 result.schema_types_new_by_name.insert("db1_email");
1543 result.schema_types_new_by_name.insert("db1_message");
1544 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1545 db1_schema, /*database=*/"db1",
1546 /*ignore_errors_and_delete_documents=*/false)),
1547 IsOkAndHolds(EqualsSetSchemaResult(result)));
1548 result = SchemaStore::SetSchemaResult();
1549 result.success = true;
1550 result.schema_types_new_by_name.insert("db2_email");
1551 result.schema_types_new_by_name.insert("db2_message");
1552 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1553 db2_schema, /*database=*/"db2",
1554 /*ignore_errors_and_delete_documents=*/false)),
1555 IsOkAndHolds(EqualsSetSchemaResult(result)));
1556 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_full_schema,
1557 schema_store->GetSchema());
1558 EXPECT_THAT(*actual_full_schema, EqualsProto(expected_full_schema));
1559
1560 // Make db2 invalid by duplicating a property name
1561 PropertyConfigProto prop =
1562 PropertyConfigBuilder()
1563 .SetName("prop0")
1564 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
1565 .SetCardinality(CARDINALITY_OPTIONAL)
1566 .Build();
1567 SchemaProto db2_schema_incompatible = SchemaBuilder()
1568 .AddType(SchemaTypeConfigBuilder()
1569 .SetType("db2_email")
1570 .SetDatabase("db2")
1571 .AddProperty(prop)
1572 .AddProperty(prop))
1573 .Build();
1574 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1575 db2_schema_incompatible,
1576 /*database=*/"db2",
1577 /*ignore_errors_and_delete_documents=*/false)),
1578 StatusIs(libtextclassifier3::StatusCode::ALREADY_EXISTS));
1579
1580 // Check the schema, this should not have changed
1581 EXPECT_THAT(schema_store->GetSchema(),
1582 IsOkAndHolds(Pointee(EqualsProto(expected_full_schema))));
1583 EXPECT_THAT(schema_store->GetSchema("db1"),
1584 IsOkAndHolds(EqualsProto(db1_schema)));
1585 EXPECT_THAT(schema_store->GetSchema("db2"),
1586 IsOkAndHolds(EqualsProto(db2_schema)));
1587 }
1588
TEST_F(SchemaStoreTest,SetSchemaWithMultipleDbFails)1589 TEST_F(SchemaStoreTest, SetSchemaWithMultipleDbFails) {
1590 ICING_ASSERT_OK_AND_ASSIGN(
1591 std::unique_ptr<SchemaStore> schema_store,
1592 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1593 feature_flags_.get(),
1594 /*initialize_stats=*/nullptr));
1595
1596 SchemaProto combined_schema =
1597 SchemaBuilder()
1598 .AddType(
1599 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1600 .AddType(SchemaTypeConfigBuilder()
1601 .SetType("db2_message")
1602 .SetDatabase("db2"))
1603 .AddType(
1604 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1605 .AddType(SchemaTypeConfigBuilder()
1606 .SetType("db1_message")
1607 .SetDatabase("db1"))
1608 .Build();
1609 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1610 combined_schema, /*database=*/"db1",
1611 /*ignore_errors_and_delete_documents=*/false)),
1612 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
1613 }
1614
TEST_F(SchemaStoreTest,SetSchemaWithMismatchedDbFails)1615 TEST_F(SchemaStoreTest, SetSchemaWithMismatchedDbFails) {
1616 ICING_ASSERT_OK_AND_ASSIGN(
1617 std::unique_ptr<SchemaStore> schema_store,
1618 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1619 feature_flags_.get(),
1620 /*initialize_stats=*/nullptr));
1621
1622 SchemaProto schema =
1623 SchemaBuilder()
1624 // This type does not explicitly set its database, so it defaults to
1625 // the empty database.
1626 .AddType(SchemaTypeConfigBuilder().SetType("db1_email"))
1627 .AddType(SchemaTypeConfigBuilder()
1628 .SetType("db1_message")
1629 .SetDatabase("db1"))
1630 .Build();
1631
1632 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1633 schema, /*database=*/"db1",
1634 /*ignore_errors_and_delete_documents=*/false)),
1635 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
1636
1637 schema =
1638 SchemaBuilder()
1639 .AddType(
1640 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1641 .AddType(SchemaTypeConfigBuilder()
1642 .SetType("db1_message")
1643 .SetDatabase("db1"))
1644 .Build();
1645 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1646 schema, /*database=*/"db_mismatch",
1647 /*ignore_errors_and_delete_documents=*/false)),
1648 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
1649
1650 schema =
1651 SchemaBuilder()
1652 .AddType(
1653 SchemaTypeConfigBuilder().SetType("db1_email").SetDatabase("db1"))
1654 .AddType(SchemaTypeConfigBuilder()
1655 .SetType("db1_message")
1656 .SetDatabase("db1"))
1657 .AddType(
1658 SchemaTypeConfigBuilder().SetType("db2_email").SetDatabase("db2"))
1659 .AddType(SchemaTypeConfigBuilder()
1660 .SetType("db2_message")
1661 .SetDatabase("db2"))
1662 .Build();
1663 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1664 schema, /*database=*/"",
1665 /*ignore_errors_and_delete_documents=*/false)),
1666 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
1667 }
1668
TEST_F(SchemaStoreTest,SetSchemaWithDuplicateTypeNameAcrossDifferentDbFails)1669 TEST_F(SchemaStoreTest, SetSchemaWithDuplicateTypeNameAcrossDifferentDbFails) {
1670 ICING_ASSERT_OK_AND_ASSIGN(
1671 std::unique_ptr<SchemaStore> schema_store,
1672 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1673 feature_flags_.get(),
1674 /*initialize_stats=*/nullptr));
1675
1676 // Set schema for the first time
1677 SchemaProto db1_schema =
1678 SchemaBuilder()
1679 .AddType(
1680 SchemaTypeConfigBuilder().SetType("email").SetDatabase("db1"))
1681 .AddType(SchemaTypeConfigBuilder()
1682 .SetType("db1_message")
1683 .SetDatabase("db1"))
1684 .Build();
1685 SchemaStore::SetSchemaResult result;
1686 result.success = true;
1687 result.schema_types_new_by_name.insert("email");
1688 result.schema_types_new_by_name.insert("db1_message");
1689 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1690 db1_schema, /*database=*/"db1",
1691 /*ignore_errors_and_delete_documents=*/false)),
1692 IsOkAndHolds(EqualsSetSchemaResult(result)));
1693 EXPECT_THAT(schema_store->GetSchema(),
1694 IsOkAndHolds(Pointee(EqualsProto(db1_schema))));
1695 EXPECT_THAT(schema_store->GetSchema("db1"),
1696 IsOkAndHolds(EqualsProto(db1_schema)));
1697
1698 // Set schema in db2 with the same type name
1699 SchemaProto db2_schema =
1700 SchemaBuilder()
1701 .AddType(
1702 SchemaTypeConfigBuilder().SetType("email").SetDatabase("db2"))
1703 .AddType(SchemaTypeConfigBuilder()
1704 .SetType("db2_message")
1705 .SetDatabase("db2"))
1706 .Build();
1707 EXPECT_THAT(schema_store->SetSchema(CreateSetSchemaRequestProto(
1708 db2_schema, /*database=*/"db2",
1709 /*ignore_errors_and_delete_documents=*/false)),
1710 StatusIs(libtextclassifier3::StatusCode::ALREADY_EXISTS));
1711
1712 // Check schema, this should not have changed
1713 EXPECT_THAT(schema_store->GetSchema(),
1714 IsOkAndHolds(Pointee(EqualsProto(db1_schema))));
1715 EXPECT_THAT(schema_store->GetSchema("db1"),
1716 IsOkAndHolds(EqualsProto(db1_schema)));
1717 }
1718
TEST_F(SchemaStoreTest,SetSchemaWithAddedTypeOk)1719 TEST_F(SchemaStoreTest, SetSchemaWithAddedTypeOk) {
1720 ICING_ASSERT_OK_AND_ASSIGN(
1721 std::unique_ptr<SchemaStore> schema_store,
1722 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1723 feature_flags_.get()));
1724
1725 SchemaProto schema = SchemaBuilder()
1726 .AddType(SchemaTypeConfigBuilder().SetType("email"))
1727 .Build();
1728
1729 // Set it for the first time
1730 SchemaStore::SetSchemaResult result;
1731 result.success = true;
1732 result.schema_types_new_by_name.insert("email");
1733 EXPECT_THAT(schema_store->SetSchema(
1734 schema, /*ignore_errors_and_delete_documents=*/false),
1735 IsOkAndHolds(EqualsSetSchemaResult(result)));
1736 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
1737 schema_store->GetSchema());
1738 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1739
1740 // Add a type, shouldn't affect the index or cached SchemaTypeIds
1741 schema = SchemaBuilder(schema)
1742 .AddType(SchemaTypeConfigBuilder().SetType("new_type"))
1743 .Build();
1744
1745 // Set the compatible schema
1746 result = SchemaStore::SetSchemaResult();
1747 result.success = true;
1748 result.schema_types_new_by_name.insert("new_type");
1749 EXPECT_THAT(schema_store->SetSchema(
1750 schema, /*ignore_errors_and_delete_documents=*/false),
1751 IsOkAndHolds(EqualsSetSchemaResult(result)));
1752 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
1753 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1754 }
1755
TEST_F(SchemaStoreTest,SetSchemaWithDeletedTypeOk)1756 TEST_F(SchemaStoreTest, SetSchemaWithDeletedTypeOk) {
1757 ICING_ASSERT_OK_AND_ASSIGN(
1758 std::unique_ptr<SchemaStore> schema_store,
1759 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1760 feature_flags_.get()));
1761
1762 SchemaProto schema =
1763 SchemaBuilder()
1764 .AddType(SchemaTypeConfigBuilder().SetType("email"))
1765 .AddType(SchemaTypeConfigBuilder().SetType("message"))
1766 .Build();
1767
1768 // Set it for the first time
1769 SchemaStore::SetSchemaResult result;
1770 result.success = true;
1771 result.schema_types_new_by_name.insert("email");
1772 result.schema_types_new_by_name.insert("message");
1773 EXPECT_THAT(schema_store->SetSchema(
1774 schema, /*ignore_errors_and_delete_documents=*/false),
1775 IsOkAndHolds(EqualsSetSchemaResult(result)));
1776 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
1777 schema_store->GetSchema());
1778 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1779
1780 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId old_email_schema_type_id,
1781 schema_store->GetSchemaTypeId("email"));
1782 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId old_message_schema_type_id,
1783 schema_store->GetSchemaTypeId("message"));
1784
1785 // Remove "email" type, this also changes previous SchemaTypeIds
1786 schema = SchemaBuilder()
1787 .AddType(SchemaTypeConfigBuilder().SetType("message"))
1788 .Build();
1789
1790 SchemaStore::SetSchemaResult incompatible_result;
1791 incompatible_result.success = false;
1792 incompatible_result.old_schema_type_ids_changed.emplace(
1793 old_message_schema_type_id);
1794 incompatible_result.schema_types_deleted_by_name.emplace("email");
1795 incompatible_result.schema_types_deleted_by_id.emplace(
1796 old_email_schema_type_id);
1797
1798 // Can't set the incompatible schema
1799 EXPECT_THAT(schema_store->SetSchema(
1800 schema, /*ignore_errors_and_delete_documents=*/false),
1801 IsOkAndHolds(EqualsSetSchemaResult(incompatible_result)));
1802
1803 SchemaStore::SetSchemaResult force_result;
1804 force_result.success = true;
1805 force_result.old_schema_type_ids_changed.emplace(old_message_schema_type_id);
1806 force_result.schema_types_deleted_by_name.emplace("email");
1807 force_result.schema_types_deleted_by_id.emplace(old_email_schema_type_id);
1808
1809 // Force set the incompatible schema
1810 EXPECT_THAT(schema_store->SetSchema(
1811 schema, /*ignore_errors_and_delete_documents=*/true),
1812 IsOkAndHolds(EqualsSetSchemaResult(force_result)));
1813 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
1814 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1815 }
1816
TEST_F(SchemaStoreTest,SetSchemaWithReorderedTypesOk)1817 TEST_F(SchemaStoreTest, SetSchemaWithReorderedTypesOk) {
1818 ICING_ASSERT_OK_AND_ASSIGN(
1819 std::unique_ptr<SchemaStore> schema_store,
1820 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1821 feature_flags_.get()));
1822
1823 SchemaProto schema =
1824 SchemaBuilder()
1825 .AddType(SchemaTypeConfigBuilder().SetType("email"))
1826 .AddType(SchemaTypeConfigBuilder().SetType("message"))
1827 .Build();
1828
1829 // Set it for the first time
1830 SchemaStore::SetSchemaResult result;
1831 result.success = true;
1832 result.schema_types_new_by_name.insert("email");
1833 result.schema_types_new_by_name.insert("message");
1834 EXPECT_THAT(schema_store->SetSchema(
1835 schema, /*ignore_errors_and_delete_documents=*/false),
1836 IsOkAndHolds(EqualsSetSchemaResult(result)));
1837 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
1838 schema_store->GetSchema());
1839 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1840
1841 // Reorder the types
1842 SchemaProto reordered_schema =
1843 SchemaBuilder()
1844 .AddType(SchemaTypeConfigBuilder().SetType("message"))
1845 .AddType(SchemaTypeConfigBuilder().SetType("email"))
1846 .Build();
1847
1848 // Set the compatible schema and verify with GetSchema
1849 if (feature_flags_->enable_schema_database()) {
1850 // Setting reordered types is a no-op for the new set schema after schema
1851 // database is enabled. So everything should be the same as before.
1852 result = SchemaStore::SetSchemaResult();
1853 result.success = true;
1854 EXPECT_THAT(
1855 schema_store->SetSchema(reordered_schema,
1856 /*ignore_errors_and_delete_documents=*/false),
1857 IsOkAndHolds(EqualsSetSchemaResult(result)));
1858
1859 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
1860 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1861 } else {
1862 // Since we assign SchemaTypeIds based on order in the SchemaProto, this
1863 // will
1864 // cause SchemaTypeIds to change
1865 result = SchemaStore::SetSchemaResult();
1866 result.success = true;
1867 result.old_schema_type_ids_changed.emplace(
1868 0); // Old SchemaTypeId of "email"
1869 result.old_schema_type_ids_changed.emplace(
1870 1); // Old SchemaTypeId of "message"
1871
1872 // Set the compatible schema
1873 EXPECT_THAT(schema_store->SetSchema(
1874 schema, /*ignore_errors_and_delete_documents=*/false),
1875 IsOkAndHolds(EqualsSetSchemaResult(result)));
1876 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
1877 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1878 }
1879 }
1880
TEST_F(SchemaStoreTest,IndexedPropertyChangeRequiresReindexingOk)1881 TEST_F(SchemaStoreTest, IndexedPropertyChangeRequiresReindexingOk) {
1882 ICING_ASSERT_OK_AND_ASSIGN(
1883 std::unique_ptr<SchemaStore> schema_store,
1884 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1885 feature_flags_.get()));
1886
1887 SchemaProto schema =
1888 SchemaBuilder()
1889 .AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
1890 // Add an unindexed property
1891 PropertyConfigBuilder()
1892 .SetName("subject")
1893 .SetDataType(TYPE_STRING)
1894 .SetCardinality(CARDINALITY_OPTIONAL)))
1895 .Build();
1896
1897 // Set it for the first time
1898 SchemaStore::SetSchemaResult result;
1899 result.success = true;
1900 result.schema_types_new_by_name.insert("email");
1901 EXPECT_THAT(schema_store->SetSchema(
1902 schema, /*ignore_errors_and_delete_documents=*/false),
1903 IsOkAndHolds(EqualsSetSchemaResult(result)));
1904 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
1905 schema_store->GetSchema());
1906 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1907
1908 // Make a previously unindexed property indexed
1909 schema = SchemaBuilder()
1910 .AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
1911 PropertyConfigBuilder()
1912 .SetName("subject")
1913 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
1914 .SetCardinality(CARDINALITY_OPTIONAL)))
1915 .Build();
1916
1917 // Set the compatible schema
1918 result = SchemaStore::SetSchemaResult();
1919 result.success = true;
1920 result.schema_types_index_incompatible_by_name.insert("email");
1921 EXPECT_THAT(schema_store->SetSchema(
1922 schema, /*ignore_errors_and_delete_documents=*/false),
1923 IsOkAndHolds(EqualsSetSchemaResult(result)));
1924 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
1925 EXPECT_THAT(*actual_schema, EqualsProto(schema));
1926 }
1927
TEST_F(SchemaStoreTest,IndexNestedDocumentsChangeRequiresReindexingOk)1928 TEST_F(SchemaStoreTest, IndexNestedDocumentsChangeRequiresReindexingOk) {
1929 ICING_ASSERT_OK_AND_ASSIGN(
1930 std::unique_ptr<SchemaStore> schema_store,
1931 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
1932 feature_flags_.get()));
1933
1934 // Make two schemas. One that sets index_nested_properties to false and one
1935 // that sets it to true.
1936 SchemaTypeConfigProto email_type_config =
1937 SchemaTypeConfigBuilder()
1938 .SetType("email")
1939 .AddProperty(PropertyConfigBuilder()
1940 .SetName("subject")
1941 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
1942 .SetCardinality(CARDINALITY_OPTIONAL))
1943 .Build();
1944 SchemaProto no_nested_index_schema =
1945 SchemaBuilder()
1946 .AddType(email_type_config)
1947 .AddType(SchemaTypeConfigBuilder().SetType("person").AddProperty(
1948 PropertyConfigBuilder()
1949 .SetName("emails")
1950 .SetDataTypeDocument("email",
1951 /*index_nested_properties=*/false)
1952 .SetCardinality(CARDINALITY_REPEATED)))
1953 .Build();
1954
1955 SchemaProto nested_index_schema =
1956 SchemaBuilder()
1957 .AddType(email_type_config)
1958 .AddType(SchemaTypeConfigBuilder().SetType("person").AddProperty(
1959 PropertyConfigBuilder()
1960 .SetName("emails")
1961 .SetDataTypeDocument("email",
1962 /*index_nested_properties=*/true)
1963 .SetCardinality(CARDINALITY_REPEATED)))
1964 .Build();
1965
1966 // Set schema with index_nested_properties=false to start.
1967 SchemaStore::SetSchemaResult result;
1968 result.success = true;
1969 result.schema_types_new_by_name.insert("email");
1970 result.schema_types_new_by_name.insert("person");
1971 EXPECT_THAT(
1972 schema_store->SetSchema(no_nested_index_schema,
1973 /*ignore_errors_and_delete_documents=*/false),
1974 IsOkAndHolds(EqualsSetSchemaResult(result)));
1975 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
1976 schema_store->GetSchema());
1977 EXPECT_THAT(*actual_schema, EqualsProto(no_nested_index_schema));
1978
1979 // Set schema with index_nested_properties=true and confirm that the change to
1980 // 'person' is index incompatible.
1981 result = SchemaStore::SetSchemaResult();
1982 result.success = true;
1983 result.schema_types_index_incompatible_by_name.insert("person");
1984 EXPECT_THAT(
1985 schema_store->SetSchema(nested_index_schema,
1986 /*ignore_errors_and_delete_documents=*/false),
1987 IsOkAndHolds(EqualsSetSchemaResult(result)));
1988 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
1989 EXPECT_THAT(*actual_schema, EqualsProto(nested_index_schema));
1990
1991 // Set schema with index_nested_properties=false and confirm that the change
1992 // to 'person' is index incompatible.
1993 result = SchemaStore::SetSchemaResult();
1994 result.success = true;
1995 result.schema_types_index_incompatible_by_name.insert("person");
1996 EXPECT_THAT(
1997 schema_store->SetSchema(no_nested_index_schema,
1998 /*ignore_errors_and_delete_documents=*/false),
1999 IsOkAndHolds(EqualsSetSchemaResult(result)));
2000 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
2001 EXPECT_THAT(*actual_schema, EqualsProto(no_nested_index_schema));
2002 }
2003
TEST_F(SchemaStoreTest,SetSchemaWithIncompatibleTypesOk)2004 TEST_F(SchemaStoreTest, SetSchemaWithIncompatibleTypesOk) {
2005 ICING_ASSERT_OK_AND_ASSIGN(
2006 std::unique_ptr<SchemaStore> schema_store,
2007 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2008 feature_flags_.get()));
2009
2010 SchemaProto schema =
2011 SchemaBuilder()
2012 .AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
2013 // Add a STRING property
2014 PropertyConfigBuilder()
2015 .SetName("subject")
2016 .SetDataType(TYPE_STRING)
2017 .SetCardinality(CARDINALITY_OPTIONAL)))
2018 .Build();
2019
2020 // Set it for the first time
2021 SchemaStore::SetSchemaResult result;
2022 result.success = true;
2023 result.schema_types_new_by_name.insert("email");
2024 EXPECT_THAT(schema_store->SetSchema(
2025 schema, /*ignore_errors_and_delete_documents=*/false),
2026 IsOkAndHolds(EqualsSetSchemaResult(result)));
2027 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2028 schema_store->GetSchema());
2029 EXPECT_THAT(*actual_schema, EqualsProto(schema));
2030
2031 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId old_email_schema_type_id,
2032 schema_store->GetSchemaTypeId("email"));
2033
2034 // Make a previously STRING property into DOUBLE
2035 schema = SchemaBuilder()
2036 .AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
2037 // Add a STRING property
2038 PropertyConfigBuilder()
2039 .SetName("subject")
2040 .SetDataType(TYPE_DOUBLE)
2041 .SetCardinality(CARDINALITY_OPTIONAL)))
2042 .Build();
2043
2044 SchemaStore::SetSchemaResult incompatible_result;
2045 incompatible_result.success = false;
2046 incompatible_result.schema_types_incompatible_by_name.emplace("email");
2047 incompatible_result.schema_types_incompatible_by_id.emplace(
2048 old_email_schema_type_id);
2049
2050 // Can't set the incompatible schema
2051 EXPECT_THAT(schema_store->SetSchema(
2052 schema, /*ignore_errors_and_delete_documents=*/false),
2053 IsOkAndHolds(EqualsSetSchemaResult(incompatible_result)));
2054
2055 SchemaStore::SetSchemaResult force_result;
2056 force_result.success = true;
2057 force_result.schema_types_incompatible_by_name.emplace("email");
2058 force_result.schema_types_incompatible_by_id.emplace(
2059 old_email_schema_type_id);
2060
2061 // Force set the incompatible schema
2062 EXPECT_THAT(schema_store->SetSchema(
2063 schema, /*ignore_errors_and_delete_documents=*/true),
2064 IsOkAndHolds(EqualsSetSchemaResult(force_result)));
2065 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
2066 EXPECT_THAT(*actual_schema, EqualsProto(schema));
2067 }
2068
TEST_F(SchemaStoreTest,SetSchemaWithIncompatibleNestedTypesOk)2069 TEST_F(SchemaStoreTest, SetSchemaWithIncompatibleNestedTypesOk) {
2070 ICING_ASSERT_OK_AND_ASSIGN(
2071 std::unique_ptr<SchemaStore> schema_store,
2072 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2073 feature_flags_.get()));
2074
2075 // 1. Create a ContactPoint type with a repeated property and set that schema
2076 SchemaTypeConfigBuilder contact_point_repeated_label =
2077 SchemaTypeConfigBuilder()
2078 .SetType("ContactPoint")
2079 .AddProperty(
2080 PropertyConfigBuilder()
2081 .SetName("label")
2082 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2083 .SetCardinality(CARDINALITY_REPEATED));
2084 SchemaProto old_schema =
2085 SchemaBuilder().AddType(contact_point_repeated_label).Build();
2086 ICING_EXPECT_OK(schema_store->SetSchema(
2087 old_schema, /*ignore_errors_and_delete_documents=*/false));
2088 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId old_contact_point_type_id,
2089 schema_store->GetSchemaTypeId("ContactPoint"));
2090
2091 // 2. Create a type that references the ContactPoint type and make a backwards
2092 // incompatible change to ContactPoint
2093 SchemaTypeConfigBuilder contact_point_optional_label =
2094 SchemaTypeConfigBuilder()
2095 .SetType("ContactPoint")
2096 .AddProperty(
2097 PropertyConfigBuilder()
2098 .SetName("label")
2099 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2100 .SetCardinality(CARDINALITY_OPTIONAL));
2101 SchemaTypeConfigBuilder person =
2102 SchemaTypeConfigBuilder().SetType("Person").AddProperty(
2103 PropertyConfigBuilder()
2104 .SetName("contactPoints")
2105 .SetDataTypeDocument("ContactPoint",
2106 /*index_nested_properties=*/true)
2107 .SetCardinality(CARDINALITY_REPEATED));
2108 SchemaProto new_schema = SchemaBuilder()
2109 .AddType(contact_point_optional_label)
2110 .AddType(person)
2111 .Build();
2112
2113 // 3. SetSchema should fail with ignore_errors_and_delete_documents=false and
2114 // the old schema should remain
2115 SchemaStore::SetSchemaResult expected_result;
2116 expected_result.success = false;
2117 expected_result.schema_types_incompatible_by_name.insert("ContactPoint");
2118 expected_result.schema_types_incompatible_by_id.insert(
2119 old_contact_point_type_id);
2120 expected_result.schema_types_new_by_name.insert("Person");
2121 EXPECT_THAT(
2122 schema_store->SetSchema(new_schema,
2123 /*ignore_errors_and_delete_documents=*/false),
2124 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2125 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2126 schema_store->GetSchema());
2127 EXPECT_THAT(*actual_schema, EqualsProto(old_schema));
2128
2129 // 4. SetSchema should succeed with ignore_errors_and_delete_documents=true
2130 // and the new schema should be set
2131 expected_result.success = true;
2132 EXPECT_THAT(
2133 schema_store->SetSchema(new_schema,
2134 /*ignore_errors_and_delete_documents=*/true),
2135 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2136 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
2137 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2138 }
2139
TEST_F(SchemaStoreTest,SetSchemaWithIndexIncompatibleNestedTypesOk)2140 TEST_F(SchemaStoreTest, SetSchemaWithIndexIncompatibleNestedTypesOk) {
2141 ICING_ASSERT_OK_AND_ASSIGN(
2142 std::unique_ptr<SchemaStore> schema_store,
2143 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2144 feature_flags_.get()));
2145
2146 // 1. Create a ContactPoint type with label that matches prefix and set that
2147 // schema
2148 SchemaTypeConfigBuilder contact_point_prefix_label =
2149 SchemaTypeConfigBuilder()
2150 .SetType("ContactPoint")
2151 .AddProperty(
2152 PropertyConfigBuilder()
2153 .SetName("label")
2154 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2155 .SetCardinality(CARDINALITY_REPEATED));
2156 SchemaProto old_schema =
2157 SchemaBuilder().AddType(contact_point_prefix_label).Build();
2158 ICING_EXPECT_OK(schema_store->SetSchema(
2159 old_schema, /*ignore_errors_and_delete_documents=*/false));
2160
2161 // 2. Create a type that references the ContactPoint type and make a index
2162 // backwards incompatible change to ContactPoint
2163 SchemaTypeConfigBuilder contact_point_exact_label =
2164 SchemaTypeConfigBuilder()
2165 .SetType("ContactPoint")
2166 .AddProperty(PropertyConfigBuilder()
2167 .SetName("label")
2168 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
2169 .SetCardinality(CARDINALITY_REPEATED));
2170 SchemaTypeConfigBuilder person =
2171 SchemaTypeConfigBuilder().SetType("Person").AddProperty(
2172 PropertyConfigBuilder()
2173 .SetName("contactPoints")
2174 .SetDataTypeDocument("ContactPoint",
2175 /*index_nested_properties=*/true)
2176 .SetCardinality(CARDINALITY_REPEATED));
2177 SchemaProto new_schema = SchemaBuilder()
2178 .AddType(contact_point_exact_label)
2179 .AddType(person)
2180 .Build();
2181
2182 // SetSchema should succeed, and only ContactPoint should be in
2183 // schema_types_index_incompatible_by_name.
2184 SchemaStore::SetSchemaResult expected_result;
2185 expected_result.success = true;
2186 expected_result.schema_types_index_incompatible_by_name.insert(
2187 "ContactPoint");
2188 expected_result.schema_types_new_by_name.insert("Person");
2189 EXPECT_THAT(
2190 schema_store->SetSchema(new_schema,
2191 /*ignore_errors_and_delete_documents=*/false),
2192 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2193 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2194 schema_store->GetSchema());
2195 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2196 }
2197
TEST_F(SchemaStoreTest,SetSchemaWithCompatibleNestedTypesOk)2198 TEST_F(SchemaStoreTest, SetSchemaWithCompatibleNestedTypesOk) {
2199 ICING_ASSERT_OK_AND_ASSIGN(
2200 std::unique_ptr<SchemaStore> schema_store,
2201 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2202 feature_flags_.get()));
2203
2204 // 1. Create a ContactPoint type with a optional property and set that schema
2205 SchemaTypeConfigBuilder contact_point_optional_label =
2206 SchemaTypeConfigBuilder()
2207 .SetType("ContactPoint")
2208 .AddProperty(
2209 PropertyConfigBuilder()
2210 .SetName("label")
2211 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2212 .SetCardinality(CARDINALITY_OPTIONAL));
2213 SchemaProto old_schema =
2214 SchemaBuilder().AddType(contact_point_optional_label).Build();
2215 ICING_EXPECT_OK(schema_store->SetSchema(
2216 old_schema, /*ignore_errors_and_delete_documents=*/false));
2217
2218 // 2. Create a type that references the ContactPoint type and make a backwards
2219 // compatible change to ContactPoint
2220 SchemaTypeConfigBuilder contact_point_repeated_label =
2221 SchemaTypeConfigBuilder()
2222 .SetType("ContactPoint")
2223 .AddProperty(
2224 PropertyConfigBuilder()
2225 .SetName("label")
2226 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2227 .SetCardinality(CARDINALITY_REPEATED));
2228 SchemaTypeConfigBuilder person =
2229 SchemaTypeConfigBuilder().SetType("Person").AddProperty(
2230 PropertyConfigBuilder()
2231 .SetName("contactPoints")
2232 .SetDataTypeDocument("ContactPoint",
2233 /*index_nested_properties=*/true)
2234 .SetCardinality(CARDINALITY_REPEATED));
2235 SchemaProto new_schema = SchemaBuilder()
2236 .AddType(contact_point_repeated_label)
2237 .AddType(person)
2238 .Build();
2239
2240 // 3. SetSchema should succeed, and only ContactPoint should be in
2241 // schema_types_changed_fully_compatible_by_name.
2242 SchemaStore::SetSchemaResult expected_result;
2243 expected_result.success = true;
2244 expected_result.schema_types_changed_fully_compatible_by_name.insert(
2245 "ContactPoint");
2246 expected_result.schema_types_new_by_name.insert("Person");
2247 EXPECT_THAT(schema_store->SetSchema(
2248 new_schema, /*ignore_errors_and_delete_documents=*/false),
2249 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2250 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2251 schema_store->GetSchema());
2252 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2253 }
2254
TEST_F(SchemaStoreTest,SetSchemaWithAddedIndexableNestedTypeOk)2255 TEST_F(SchemaStoreTest, SetSchemaWithAddedIndexableNestedTypeOk) {
2256 ICING_ASSERT_OK_AND_ASSIGN(
2257 std::unique_ptr<SchemaStore> schema_store,
2258 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2259 feature_flags_.get()));
2260
2261 // 1. Create a ContactPoint type with a optional property, and a type that
2262 // references the ContactPoint type.
2263 SchemaTypeConfigBuilder contact_point =
2264 SchemaTypeConfigBuilder()
2265 .SetType("ContactPoint")
2266 .AddProperty(
2267 PropertyConfigBuilder()
2268 .SetName("label")
2269 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2270 .SetCardinality(CARDINALITY_REPEATED));
2271 SchemaTypeConfigBuilder person =
2272 SchemaTypeConfigBuilder().SetType("Person").AddProperty(
2273 PropertyConfigBuilder()
2274 .SetName("contactPoints")
2275 .SetDataTypeDocument("ContactPoint",
2276 /*index_nested_properties=*/true)
2277 .SetCardinality(CARDINALITY_REPEATED));
2278 SchemaProto old_schema =
2279 SchemaBuilder().AddType(contact_point).AddType(person).Build();
2280 ICING_EXPECT_OK(schema_store->SetSchema(
2281 old_schema, /*ignore_errors_and_delete_documents=*/false));
2282
2283 // 2. Add another nested document property to "Person" that has type
2284 // "ContactPoint"
2285 SchemaTypeConfigBuilder new_person =
2286 SchemaTypeConfigBuilder()
2287 .SetType("Person")
2288 .AddProperty(
2289 PropertyConfigBuilder()
2290 .SetName("contactPoints")
2291 .SetDataTypeDocument("ContactPoint",
2292 /*index_nested_properties=*/true)
2293 .SetCardinality(CARDINALITY_REPEATED))
2294 .AddProperty(
2295 PropertyConfigBuilder()
2296 .SetName("anotherContactPoint")
2297 .SetDataTypeDocument("ContactPoint",
2298 /*index_nested_properties=*/true)
2299 .SetCardinality(CARDINALITY_REPEATED));
2300 SchemaProto new_schema =
2301 SchemaBuilder().AddType(contact_point).AddType(new_person).Build();
2302
2303 // 3. Set to new schema. "Person" should be index-incompatible since we need
2304 // to index an additional property: 'anotherContactPoint.label'.
2305 // - "Person" is also considered join-incompatible since the added nested
2306 // document property could also contain a joinable property.
2307 SchemaStore::SetSchemaResult expected_result;
2308 expected_result.success = true;
2309 expected_result.schema_types_index_incompatible_by_name.insert("Person");
2310 expected_result.schema_types_join_incompatible_by_name.insert("Person");
2311
2312 EXPECT_THAT(schema_store->SetSchema(
2313 new_schema, /*ignore_errors_and_delete_documents=*/false),
2314 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2315 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2316 schema_store->GetSchema());
2317 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2318 }
2319
TEST_F(SchemaStoreTest,SetSchemaWithAddedJoinableNestedTypeOk)2320 TEST_F(SchemaStoreTest, SetSchemaWithAddedJoinableNestedTypeOk) {
2321 ICING_ASSERT_OK_AND_ASSIGN(
2322 std::unique_ptr<SchemaStore> schema_store,
2323 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2324 feature_flags_.get()));
2325
2326 // 1. Create a ContactPoint type with a optional property, and a type that
2327 // references the ContactPoint type.
2328 SchemaTypeConfigBuilder contact_point =
2329 SchemaTypeConfigBuilder()
2330 .SetType("ContactPoint")
2331 .AddProperty(
2332 PropertyConfigBuilder()
2333 .SetName("label")
2334 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
2335 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
2336 DELETE_PROPAGATION_TYPE_NONE)
2337 .SetCardinality(CARDINALITY_REQUIRED));
2338 SchemaTypeConfigBuilder person =
2339 SchemaTypeConfigBuilder().SetType("Person").AddProperty(
2340 PropertyConfigBuilder()
2341 .SetName("contactPoints")
2342 .SetDataTypeDocument("ContactPoint",
2343 /*index_nested_properties=*/true)
2344 .SetCardinality(CARDINALITY_OPTIONAL));
2345 SchemaProto old_schema =
2346 SchemaBuilder().AddType(contact_point).AddType(person).Build();
2347 ICING_EXPECT_OK(schema_store->SetSchema(
2348 old_schema, /*ignore_errors_and_delete_documents=*/false));
2349
2350 // 2. Add another nested document property to "Person" that has type
2351 // "ContactPoint", but make it non-indexable
2352 SchemaTypeConfigBuilder new_person =
2353 SchemaTypeConfigBuilder()
2354 .SetType("Person")
2355 .AddProperty(
2356 PropertyConfigBuilder()
2357 .SetName("contactPoints")
2358 .SetDataTypeDocument("ContactPoint",
2359 /*index_nested_properties=*/true)
2360 .SetCardinality(CARDINALITY_OPTIONAL))
2361 .AddProperty(
2362 PropertyConfigBuilder()
2363 .SetName("anotherContactPoint")
2364 .SetDataTypeDocument("ContactPoint",
2365 /*index_nested_properties=*/false)
2366 .SetCardinality(CARDINALITY_OPTIONAL));
2367 SchemaProto new_schema =
2368 SchemaBuilder().AddType(contact_point).AddType(new_person).Build();
2369
2370 // 3. Set to new schema. "Person" should be join-incompatible but
2371 // index-compatible.
2372 SchemaStore::SetSchemaResult expected_result;
2373 expected_result.success = true;
2374 expected_result.schema_types_join_incompatible_by_name.insert("Person");
2375
2376 EXPECT_THAT(schema_store->SetSchema(
2377 new_schema, /*ignore_errors_and_delete_documents=*/false),
2378 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2379 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2380 schema_store->GetSchema());
2381 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2382 }
2383
TEST_F(SchemaStoreTest,SetSchemaByUpdatingScorablePropertyOk)2384 TEST_F(SchemaStoreTest, SetSchemaByUpdatingScorablePropertyOk) {
2385 ICING_ASSERT_OK_AND_ASSIGN(
2386 std::unique_ptr<SchemaStore> schema_store,
2387 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2388 feature_flags_.get()));
2389
2390 SchemaProto old_schema =
2391 SchemaBuilder()
2392 .AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
2393 PropertyConfigBuilder()
2394 .SetName("title")
2395 .SetDataType(TYPE_STRING)
2396 .SetCardinality(CARDINALITY_REQUIRED)))
2397 .Build();
2398 SchemaProto new_schema =
2399 SchemaBuilder()
2400 .AddType(SchemaTypeConfigBuilder()
2401 .SetType("email")
2402 .AddProperty(PropertyConfigBuilder()
2403 .SetName("title")
2404 .SetDataType(TYPE_STRING)
2405 .SetCardinality(CARDINALITY_REQUIRED))
2406 .AddProperty(PropertyConfigBuilder()
2407 .SetName("score")
2408 .SetDataType(TYPE_DOUBLE)
2409 .SetScorableType(SCORABLE_TYPE_ENABLED)
2410 .SetCardinality(CARDINALITY_OPTIONAL)))
2411 .Build();
2412
2413 // Set old schema
2414 SchemaStore::SetSchemaResult expected_result;
2415 expected_result.success = true;
2416 expected_result.schema_types_new_by_name.insert("email");
2417 EXPECT_THAT(schema_store->SetSchema(
2418 old_schema, /*ignore_errors_and_delete_documents=*/false),
2419 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2420 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2421 schema_store->GetSchema());
2422 EXPECT_THAT(*actual_schema, EqualsProto(old_schema));
2423
2424 // Set new schema.
2425 // The new schema adds "score" as scorable_type ENABLED from type "email".
2426 SchemaStore::SetSchemaResult new_expected_result;
2427 new_expected_result.success = true;
2428 new_expected_result.schema_types_scorable_property_inconsistent_by_id.insert(
2429 0);
2430 new_expected_result.schema_types_changed_fully_compatible_by_name.insert(
2431 "email");
2432 EXPECT_THAT(schema_store->SetSchema(
2433 new_schema, /*ignore_errors_and_delete_documents=*/false),
2434 IsOkAndHolds(EqualsSetSchemaResult(new_expected_result)));
2435 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
2436 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2437 }
2438
TEST_F(SchemaStoreTest,SetSchemaWithReorderedSchemeTypesAndUpdatedScorablePropertyOk)2439 TEST_F(SchemaStoreTest,
2440 SetSchemaWithReorderedSchemeTypesAndUpdatedScorablePropertyOk) {
2441 ICING_ASSERT_OK_AND_ASSIGN(
2442 std::unique_ptr<SchemaStore> schema_store,
2443 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2444 feature_flags_.get()));
2445
2446 SchemaProto old_schema =
2447 SchemaBuilder()
2448 .AddType(SchemaTypeConfigBuilder().SetType("message"))
2449 .AddType(SchemaTypeConfigBuilder()
2450 .SetType("email")
2451 .AddProperty(PropertyConfigBuilder()
2452 .SetName("title")
2453 .SetDataType(TYPE_STRING)
2454 .SetCardinality(CARDINALITY_REQUIRED))
2455 .AddProperty(PropertyConfigBuilder()
2456 .SetName("score")
2457 .SetDataType(TYPE_DOUBLE)
2458 .SetScorableType(SCORABLE_TYPE_DISABLED)
2459 .SetCardinality(CARDINALITY_OPTIONAL)))
2460 .Build();
2461 // The new schema updates "score" as scorable_type ENABLED from type "email",
2462 // and it also reorders the schema types of "email" and "message".
2463 SchemaProto new_schema =
2464 SchemaBuilder()
2465 .AddType(SchemaTypeConfigBuilder()
2466 .SetType("email")
2467 .AddProperty(PropertyConfigBuilder()
2468 .SetName("title")
2469 .SetDataType(TYPE_STRING)
2470 .SetCardinality(CARDINALITY_REQUIRED))
2471 .AddProperty(PropertyConfigBuilder()
2472 .SetName("score")
2473 .SetDataType(TYPE_DOUBLE)
2474 .SetScorableType(SCORABLE_TYPE_ENABLED)
2475 .SetCardinality(CARDINALITY_OPTIONAL)))
2476 .AddType(SchemaTypeConfigBuilder().SetType("message"))
2477 .Build();
2478
2479 // Set old schema
2480 SchemaStore::SetSchemaResult expected_result;
2481 expected_result.success = true;
2482 expected_result.schema_types_new_by_name.insert("email");
2483 expected_result.schema_types_new_by_name.insert("message");
2484 EXPECT_THAT(schema_store->SetSchema(
2485 old_schema, /*ignore_errors_and_delete_documents=*/false),
2486 IsOkAndHolds(EqualsSetSchemaResult(expected_result)));
2487 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2488 schema_store->GetSchema());
2489 EXPECT_THAT(*actual_schema, EqualsProto(old_schema));
2490
2491 // Set new schema.
2492 SchemaStore::SetSchemaResult new_expected_result;
2493 new_expected_result.success = true;
2494 // Schema type id of "email" is updated to 0.
2495 SchemaTypeId email_schema_type_id = 0;
2496 new_expected_result.schema_types_scorable_property_inconsistent_by_id.insert(
2497 email_schema_type_id);
2498 new_expected_result.schema_types_changed_fully_compatible_by_name.insert(
2499 "email");
2500 new_expected_result.old_schema_type_ids_changed.insert(0);
2501 new_expected_result.old_schema_type_ids_changed.insert(1);
2502 EXPECT_THAT(schema_store->SetSchema(
2503 new_schema, /*ignore_errors_and_delete_documents=*/false),
2504 IsOkAndHolds(EqualsSetSchemaResult(new_expected_result)));
2505 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
2506 EXPECT_THAT(*actual_schema, EqualsProto(new_schema));
2507 }
2508
TEST_F(SchemaStoreTest,GetSchemaTypeId)2509 TEST_F(SchemaStoreTest, GetSchemaTypeId) {
2510 ICING_ASSERT_OK_AND_ASSIGN(
2511 std::unique_ptr<SchemaStore> schema_store,
2512 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2513 feature_flags_.get()));
2514
2515 schema_.clear_types();
2516
2517 // Add a few schema types
2518 const std::string first_type = "first";
2519 auto type = schema_.add_types();
2520 type->set_schema_type(first_type);
2521
2522 const std::string second_type = "second";
2523 type = schema_.add_types();
2524 type->set_schema_type(second_type);
2525
2526 // Set it for the first time
2527 SchemaStore::SetSchemaResult result;
2528 result.success = true;
2529 result.schema_types_new_by_name.insert(first_type);
2530 result.schema_types_new_by_name.insert(second_type);
2531 EXPECT_THAT(schema_store->SetSchema(
2532 schema_, /*ignore_errors_and_delete_documents=*/false),
2533 IsOkAndHolds(EqualsSetSchemaResult(result)));
2534
2535 EXPECT_THAT(schema_store->GetSchemaTypeId(first_type), IsOkAndHolds(0));
2536 EXPECT_THAT(schema_store->GetSchemaTypeId(second_type), IsOkAndHolds(1));
2537 }
2538
TEST_F(SchemaStoreTest,UpdateChecksumDefaultOnEmptySchemaStore)2539 TEST_F(SchemaStoreTest, UpdateChecksumDefaultOnEmptySchemaStore) {
2540 ICING_ASSERT_OK_AND_ASSIGN(
2541 std::unique_ptr<SchemaStore> schema_store,
2542 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2543 feature_flags_.get()));
2544
2545 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(Crc32()));
2546 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(Crc32()));
2547 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(Crc32()));
2548 }
2549
TEST_F(SchemaStoreTest,UpdateChecksumSameBetweenCalls)2550 TEST_F(SchemaStoreTest, UpdateChecksumSameBetweenCalls) {
2551 ICING_ASSERT_OK_AND_ASSIGN(
2552 std::unique_ptr<SchemaStore> schema_store,
2553 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2554 feature_flags_.get()));
2555
2556 SchemaProto foo_schema =
2557 SchemaBuilder().AddType(SchemaTypeConfigBuilder().SetType("foo")).Build();
2558
2559 ICING_EXPECT_OK(schema_store->SetSchema(
2560 foo_schema, /*ignore_errors_and_delete_documents=*/false));
2561
2562 ICING_ASSERT_OK_AND_ASSIGN(Crc32 checksum, schema_store->GetChecksum());
2563 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(checksum));
2564 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(checksum));
2565
2566 // Calling it again doesn't change the checksum
2567 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(checksum));
2568 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(checksum));
2569 }
2570
TEST_F(SchemaStoreTest,UpdateChecksumSameAcrossInstances)2571 TEST_F(SchemaStoreTest, UpdateChecksumSameAcrossInstances) {
2572 ICING_ASSERT_OK_AND_ASSIGN(
2573 std::unique_ptr<SchemaStore> schema_store,
2574 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2575 feature_flags_.get()));
2576
2577 SchemaProto foo_schema =
2578 SchemaBuilder().AddType(SchemaTypeConfigBuilder().SetType("foo")).Build();
2579
2580 ICING_EXPECT_OK(schema_store->SetSchema(
2581 foo_schema, /*ignore_errors_and_delete_documents=*/false));
2582
2583 ICING_ASSERT_OK_AND_ASSIGN(Crc32 checksum, schema_store->GetChecksum());
2584 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(checksum));
2585 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(checksum));
2586
2587 // Destroy the previous instance and recreate SchemaStore
2588 schema_store.reset();
2589
2590 ICING_ASSERT_OK_AND_ASSIGN(
2591 schema_store, SchemaStore::Create(&filesystem_, schema_store_dir_,
2592 &fake_clock_, feature_flags_.get()));
2593 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(checksum));
2594 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(checksum));
2595 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(checksum));
2596 }
2597
TEST_F(SchemaStoreTest,UpdateChecksumChangesOnModification)2598 TEST_F(SchemaStoreTest, UpdateChecksumChangesOnModification) {
2599 ICING_ASSERT_OK_AND_ASSIGN(
2600 std::unique_ptr<SchemaStore> schema_store,
2601 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2602 feature_flags_.get()));
2603
2604 SchemaProto foo_schema =
2605 SchemaBuilder().AddType(SchemaTypeConfigBuilder().SetType("foo")).Build();
2606
2607 ICING_EXPECT_OK(schema_store->SetSchema(
2608 foo_schema, /*ignore_errors_and_delete_documents=*/false));
2609
2610 ICING_ASSERT_OK_AND_ASSIGN(Crc32 checksum, schema_store->GetChecksum());
2611 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(checksum));
2612 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(checksum));
2613
2614 // Modifying the SchemaStore changes the checksum
2615 SchemaProto foo_bar_schema =
2616 SchemaBuilder()
2617 .AddType(SchemaTypeConfigBuilder().SetType("foo"))
2618 .AddType(SchemaTypeConfigBuilder().SetType("bar"))
2619 .Build();
2620
2621 ICING_EXPECT_OK(schema_store->SetSchema(
2622 foo_bar_schema, /*ignore_errors_and_delete_documents=*/false));
2623
2624 ICING_ASSERT_OK_AND_ASSIGN(Crc32 updated_checksum,
2625 schema_store->GetChecksum());
2626 EXPECT_THAT(updated_checksum, Not(Eq(checksum)));
2627 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(updated_checksum));
2628 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(updated_checksum));
2629 }
2630
TEST_F(SchemaStoreTest,PersistToDiskFineForEmptySchemaStore)2631 TEST_F(SchemaStoreTest, PersistToDiskFineForEmptySchemaStore) {
2632 ICING_ASSERT_OK_AND_ASSIGN(
2633 std::unique_ptr<SchemaStore> schema_store,
2634 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2635 feature_flags_.get()));
2636
2637 // Persisting is fine and shouldn't affect anything
2638 ICING_EXPECT_OK(schema_store->PersistToDisk());
2639 }
2640
TEST_F(SchemaStoreTest,UpdateChecksumAvoidsRecovery)2641 TEST_F(SchemaStoreTest, UpdateChecksumAvoidsRecovery) {
2642 ICING_ASSERT_OK_AND_ASSIGN(
2643 std::unique_ptr<SchemaStore> schema_store,
2644 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2645 feature_flags_.get()));
2646
2647 SchemaProto schema =
2648 SchemaBuilder().AddType(SchemaTypeConfigBuilder().SetType("foo")).Build();
2649
2650 ICING_EXPECT_OK(schema_store->SetSchema(
2651 schema, /*ignore_errors_and_delete_documents=*/false));
2652
2653 // UpdateChecksum should update the schema store checksum. Therefore, we
2654 // should not need a recovery on reinitialization.
2655 ICING_ASSERT_OK_AND_ASSIGN(Crc32 crc, schema_store->GetChecksum());
2656 EXPECT_THAT(schema_store->UpdateChecksum(), IsOkAndHolds(crc));
2657 EXPECT_THAT(schema_store->GetChecksum(), IsOkAndHolds(crc));
2658
2659 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2660 schema_store->GetSchema());
2661 EXPECT_THAT(*actual_schema, EqualsProto(schema));
2662
2663 // And we get the same schema back on reinitialization
2664 InitializeStatsProto initialize_stats;
2665 ICING_ASSERT_OK_AND_ASSIGN(
2666 std::unique_ptr<SchemaStore> schema_store_two,
2667 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2668 feature_flags_.get(), &initialize_stats));
2669 EXPECT_THAT(initialize_stats.schema_store_recovery_cause(),
2670 Eq(InitializeStatsProto::NONE));
2671 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store_two->GetSchema());
2672 EXPECT_THAT(*actual_schema, EqualsProto(schema));
2673
2674 // The checksum should be the same.
2675 EXPECT_THAT(schema_store_two->GetChecksum(), IsOkAndHolds(crc));
2676 EXPECT_THAT(schema_store_two->UpdateChecksum(), IsOkAndHolds(crc));
2677 EXPECT_THAT(schema_store_two->GetChecksum(), IsOkAndHolds(crc));
2678 }
2679
TEST_F(SchemaStoreTest,PersistToDiskPreservesAcrossInstances)2680 TEST_F(SchemaStoreTest, PersistToDiskPreservesAcrossInstances) {
2681 ICING_ASSERT_OK_AND_ASSIGN(
2682 std::unique_ptr<SchemaStore> schema_store,
2683 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2684 feature_flags_.get()));
2685
2686 SchemaProto schema =
2687 SchemaBuilder().AddType(SchemaTypeConfigBuilder().SetType("foo")).Build();
2688
2689 ICING_EXPECT_OK(schema_store->SetSchema(
2690 schema, /*ignore_errors_and_delete_documents=*/false));
2691
2692 // Persisting shouldn't change anything
2693 ICING_EXPECT_OK(schema_store->PersistToDisk());
2694
2695 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* actual_schema,
2696 schema_store->GetSchema());
2697 EXPECT_THAT(*actual_schema, EqualsProto(schema));
2698
2699 // Modify the schema so that something different is persisted next time
2700 schema = SchemaBuilder(schema)
2701 .AddType(SchemaTypeConfigBuilder().SetType("bar"))
2702 .Build();
2703 ICING_EXPECT_OK(schema_store->SetSchema(
2704 schema, /*ignore_errors_and_delete_documents=*/false));
2705
2706 // Should also persist on destruction
2707 schema_store.reset();
2708
2709 // And we get the same schema back on reinitialization
2710 InitializeStatsProto initialize_stats;
2711 ICING_ASSERT_OK_AND_ASSIGN(
2712 schema_store,
2713 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2714 feature_flags_.get(), &initialize_stats));
2715 EXPECT_THAT(initialize_stats.schema_store_recovery_cause(),
2716 Eq(InitializeStatsProto::NONE));
2717 ICING_ASSERT_OK_AND_ASSIGN(actual_schema, schema_store->GetSchema());
2718 EXPECT_THAT(*actual_schema, EqualsProto(schema));
2719 }
2720
TEST_F(SchemaStoreTest,SchemaStoreStorageInfoProto)2721 TEST_F(SchemaStoreTest, SchemaStoreStorageInfoProto) {
2722 ICING_ASSERT_OK_AND_ASSIGN(
2723 std::unique_ptr<SchemaStore> schema_store,
2724 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2725 feature_flags_.get()));
2726
2727 // Create a schema with two types: one simple type and one type that uses all
2728 // 64 sections.
2729 PropertyConfigProto prop =
2730 PropertyConfigBuilder()
2731 .SetName("subject")
2732 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
2733 .SetCardinality(CARDINALITY_OPTIONAL)
2734 .Build();
2735 SchemaTypeConfigBuilder full_sections_type_builder =
2736 SchemaTypeConfigBuilder().SetType("fullSectionsType");
2737 for (int i = 0; i < 64; ++i) {
2738 full_sections_type_builder.AddProperty(
2739 PropertyConfigBuilder(prop).SetName("prop" + std::to_string(i)));
2740 }
2741 SchemaProto schema =
2742 SchemaBuilder()
2743 .AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
2744 PropertyConfigBuilder(prop)))
2745 .AddType(full_sections_type_builder)
2746 .Build();
2747
2748 SchemaStore::SetSchemaResult result;
2749 result.success = true;
2750 result.schema_types_new_by_name.insert("email");
2751 result.schema_types_new_by_name.insert("fullSectionsType");
2752 EXPECT_THAT(schema_store->SetSchema(
2753 schema, /*ignore_errors_and_delete_documents=*/false),
2754 IsOkAndHolds(EqualsSetSchemaResult(result)));
2755
2756 SchemaStoreStorageInfoProto storage_info = schema_store->GetStorageInfo();
2757 EXPECT_THAT(storage_info.schema_store_size(), Ge(0));
2758 EXPECT_THAT(storage_info.num_schema_types(), Eq(2));
2759 EXPECT_THAT(storage_info.num_total_sections(), Eq(65));
2760 EXPECT_THAT(storage_info.num_schema_types_sections_exhausted(), Eq(1));
2761 }
2762
TEST_F(SchemaStoreTest,GetDebugInfo)2763 TEST_F(SchemaStoreTest, GetDebugInfo) {
2764 ICING_ASSERT_OK_AND_ASSIGN(
2765 std::unique_ptr<SchemaStore> schema_store,
2766 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2767 feature_flags_.get()));
2768
2769 // Set schema
2770 ASSERT_THAT(
2771 schema_store->SetSchema(schema_,
2772 /*ignore_errors_and_delete_documents=*/false),
2773 IsOkAndHolds(EqualsSetSchemaResult(SchemaStore::SetSchemaResult{
2774 .success = true,
2775 .schema_types_new_by_name = {schema_.types(0).schema_type()}})));
2776
2777 // Check debug info
2778 ICING_ASSERT_OK_AND_ASSIGN(SchemaDebugInfoProto out,
2779 schema_store->GetDebugInfo());
2780 EXPECT_THAT(out.schema(), EqualsProto(schema_));
2781 EXPECT_THAT(out.crc(), Gt(0));
2782 }
2783
TEST_F(SchemaStoreTest,GetDebugInfoForEmptySchemaStore)2784 TEST_F(SchemaStoreTest, GetDebugInfoForEmptySchemaStore) {
2785 ICING_ASSERT_OK_AND_ASSIGN(
2786 std::unique_ptr<SchemaStore> schema_store,
2787 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2788 feature_flags_.get()));
2789
2790 // Check debug info before setting a schema
2791 ICING_ASSERT_OK_AND_ASSIGN(SchemaDebugInfoProto out,
2792 schema_store->GetDebugInfo());
2793 SchemaDebugInfoProto expected_out;
2794 expected_out.set_crc(0);
2795 EXPECT_THAT(out, EqualsProto(expected_out));
2796 }
2797
TEST_F(SchemaStoreTest,InitializeRegenerateDerivedFilesFailure)2798 TEST_F(SchemaStoreTest, InitializeRegenerateDerivedFilesFailure) {
2799 // This test covers the first point that RegenerateDerivedFiles could fail.
2800 // This should simply result in SetSchema::Create returning an INTERNAL error.
2801
2802 {
2803 ICING_ASSERT_OK_AND_ASSIGN(
2804 std::unique_ptr<SchemaStore> schema_store,
2805 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2806 feature_flags_.get()));
2807 SchemaProto schema = SchemaBuilder()
2808 .AddType(SchemaTypeConfigBuilder().SetType("Type"))
2809 .Build();
2810 ICING_ASSERT_OK(schema_store->SetSchema(
2811 std::move(schema), /*ignore_errors_and_delete_documents=*/false));
2812 }
2813
2814 auto mock_filesystem = std::make_unique<MockFilesystem>();
2815 ON_CALL(*mock_filesystem,
2816 CreateDirectoryRecursively(HasSubstr("key_mapper_dir")))
2817 .WillByDefault(Return(false));
2818 {
2819 EXPECT_THAT(SchemaStore::Create(mock_filesystem.get(), schema_store_dir_,
2820 &fake_clock_, feature_flags_.get()),
2821 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
2822 }
2823 }
2824
TEST_F(SchemaStoreTest,SetSchemaRegenerateDerivedFilesFailure)2825 TEST_F(SchemaStoreTest, SetSchemaRegenerateDerivedFilesFailure) {
2826 // This test covers the second point that RegenerateDerivedFiles could fail.
2827 // If handled correctly, the schema store and section manager should still be
2828 // in the original, valid state.
2829 SchemaTypeConfigProto type =
2830 SchemaTypeConfigBuilder()
2831 .SetType("Type")
2832 .AddProperty(PropertyConfigBuilder()
2833 .SetName("intProp1")
2834 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
2835 .SetCardinality(CARDINALITY_OPTIONAL))
2836 .AddProperty(PropertyConfigBuilder()
2837 .SetName("stringProp1")
2838 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
2839 .SetCardinality(CARDINALITY_OPTIONAL))
2840 .Build();
2841 {
2842 ICING_ASSERT_OK_AND_ASSIGN(
2843 std::unique_ptr<SchemaStore> schema_store,
2844 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2845 feature_flags_.get()));
2846 SchemaProto schema = SchemaBuilder().AddType(type).Build();
2847 ICING_ASSERT_OK(schema_store->SetSchema(
2848 std::move(schema), /*ignore_errors_and_delete_documents=*/false));
2849 }
2850
2851 {
2852 auto mock_filesystem = std::make_unique<MockFilesystem>();
2853 ICING_ASSERT_OK_AND_ASSIGN(
2854 std::unique_ptr<SchemaStore> schema_store,
2855 SchemaStore::Create(mock_filesystem.get(), schema_store_dir_,
2856 &fake_clock_, feature_flags_.get()));
2857
2858 ON_CALL(*mock_filesystem,
2859 CreateDirectoryRecursively(HasSubstr("key_mapper_dir")))
2860 .WillByDefault(Return(false));
2861 SchemaProto schema =
2862 SchemaBuilder()
2863 .AddType(type)
2864 .AddType(SchemaTypeConfigBuilder().SetType("Type2"))
2865 .Build();
2866 EXPECT_THAT(
2867 schema_store->SetSchema(std::move(schema),
2868 /*ignore_errors_and_delete_documents=*/false),
2869 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
2870 DocumentProto document =
2871 DocumentBuilder()
2872 .SetSchema("Type")
2873 .AddInt64Property("intProp1", 1, 2, 3)
2874 .AddStringProperty("stringProp1", "foo bar baz")
2875 .Build();
2876 SectionMetadata expected_int_prop1_metadata(
2877 /*id_in=*/0, TYPE_INT64, TOKENIZER_NONE, TERM_MATCH_UNKNOWN,
2878 NUMERIC_MATCH_RANGE, EMBEDDING_INDEXING_UNKNOWN, QUANTIZATION_TYPE_NONE,
2879 "intProp1");
2880 SectionMetadata expected_string_prop1_metadata(
2881 /*id_in=*/1, TYPE_STRING, TOKENIZER_PLAIN, TERM_MATCH_EXACT,
2882 NUMERIC_MATCH_UNKNOWN, EMBEDDING_INDEXING_UNKNOWN,
2883 QUANTIZATION_TYPE_NONE, "stringProp1");
2884 ICING_ASSERT_OK_AND_ASSIGN(SectionGroup section_group,
2885 schema_store->ExtractSections(document));
2886 ASSERT_THAT(section_group.string_sections, SizeIs(1));
2887 EXPECT_THAT(section_group.string_sections.at(0).metadata,
2888 Eq(expected_string_prop1_metadata));
2889 EXPECT_THAT(section_group.string_sections.at(0).content,
2890 ElementsAre("foo bar baz"));
2891 ASSERT_THAT(section_group.integer_sections, SizeIs(1));
2892 EXPECT_THAT(section_group.integer_sections.at(0).metadata,
2893 Eq(expected_int_prop1_metadata));
2894 EXPECT_THAT(section_group.integer_sections.at(0).content,
2895 ElementsAre(1, 2, 3));
2896 }
2897 }
2898
TEST_F(SchemaStoreTest,CanCheckForPropertiesDefinedInSchema)2899 TEST_F(SchemaStoreTest, CanCheckForPropertiesDefinedInSchema) {
2900 ICING_ASSERT_OK_AND_ASSIGN(
2901 std::unique_ptr<SchemaStore> schema_store,
2902 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2903 feature_flags_.get()));
2904
2905 // Set it for the first time
2906 SchemaStore::SetSchemaResult result;
2907 result.success = true;
2908 result.schema_types_new_by_name.insert(schema_.types(0).schema_type());
2909
2910 // Don't use schema_ defined in the test suite, as we want to make sure that
2911 // the test is written correctly without referring to what the suite has
2912 // defined.
2913 SchemaProto schema =
2914 SchemaBuilder()
2915 .AddType(
2916 SchemaTypeConfigBuilder()
2917 .SetType("email")
2918 .AddProperty(
2919 // Add an indexed property so we generate
2920 // section metadata on it
2921 PropertyConfigBuilder()
2922 .SetName("subject")
2923 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
2924 .SetCardinality(CARDINALITY_OPTIONAL))
2925 .AddProperty(PropertyConfigBuilder()
2926 .SetName("timestamp")
2927 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
2928 .SetCardinality(CARDINALITY_OPTIONAL)))
2929 .Build();
2930
2931 EXPECT_THAT(schema_store->SetSchema(
2932 schema, /*ignore_errors_and_delete_documents=*/false),
2933 IsOkAndHolds(EqualsSetSchemaResult(result)));
2934 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId schema_id,
2935 schema_store->GetSchemaTypeId("email"));
2936 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(schema_id, "subject"));
2937 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(schema_id, "timestamp"));
2938 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(schema_id, "foobar"));
2939 }
2940
TEST_F(SchemaStoreTest,GetSchemaTypeIdsWithChildren)2941 TEST_F(SchemaStoreTest, GetSchemaTypeIdsWithChildren) {
2942 ICING_ASSERT_OK_AND_ASSIGN(
2943 std::unique_ptr<SchemaStore> schema_store,
2944 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
2945 feature_flags_.get()));
2946
2947 // Create a schema with the following inheritance relation:
2948 // A
2949 // / \
2950 // B E
2951 // / \
2952 // C D
2953 // |
2954 // F
2955 SchemaTypeConfigProto type_a = SchemaTypeConfigBuilder().SetType("A").Build();
2956 SchemaTypeConfigProto type_b =
2957 SchemaTypeConfigBuilder().SetType("B").AddParentType("A").Build();
2958 SchemaTypeConfigProto type_c =
2959 SchemaTypeConfigBuilder().SetType("C").AddParentType("B").Build();
2960 SchemaTypeConfigProto type_d =
2961 SchemaTypeConfigBuilder().SetType("D").AddParentType("B").Build();
2962 SchemaTypeConfigProto type_e =
2963 SchemaTypeConfigBuilder().SetType("E").AddParentType("A").Build();
2964 SchemaTypeConfigProto type_f =
2965 SchemaTypeConfigBuilder().SetType("F").AddParentType("D").Build();
2966 SchemaProto schema = SchemaBuilder()
2967 .AddType(type_a)
2968 .AddType(type_b)
2969 .AddType(type_c)
2970 .AddType(type_d)
2971 .AddType(type_e)
2972 .AddType(type_f)
2973 .Build();
2974 ICING_ASSERT_OK(schema_store->SetSchema(
2975 schema, /*ignore_errors_and_delete_documents=*/false));
2976
2977 // Get schema type id for each type.
2978 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_a_id,
2979 schema_store->GetSchemaTypeId("A"));
2980 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_b_id,
2981 schema_store->GetSchemaTypeId("B"));
2982 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_c_id,
2983 schema_store->GetSchemaTypeId("C"));
2984 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_d_id,
2985 schema_store->GetSchemaTypeId("D"));
2986 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_e_id,
2987 schema_store->GetSchemaTypeId("E"));
2988 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_f_id,
2989 schema_store->GetSchemaTypeId("F"));
2990
2991 // Check the results from GetSchemaTypeIdsWithChildren
2992 EXPECT_THAT(
2993 schema_store->GetSchemaTypeIdsWithChildren("A"),
2994 IsOkAndHolds(Pointee(UnorderedElementsAre(
2995 type_a_id, type_b_id, type_c_id, type_d_id, type_e_id, type_f_id))));
2996 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("B"),
2997 IsOkAndHolds(Pointee(UnorderedElementsAre(
2998 type_b_id, type_c_id, type_d_id, type_f_id))));
2999 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("C"),
3000 IsOkAndHolds(Pointee(UnorderedElementsAre(type_c_id))));
3001 EXPECT_THAT(
3002 schema_store->GetSchemaTypeIdsWithChildren("D"),
3003 IsOkAndHolds(Pointee(UnorderedElementsAre(type_d_id, type_f_id))));
3004 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("E"),
3005 IsOkAndHolds(Pointee(UnorderedElementsAre(type_e_id))));
3006 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("F"),
3007 IsOkAndHolds(Pointee(UnorderedElementsAre(type_f_id))));
3008 }
3009
TEST_F(SchemaStoreTest,DiamondGetSchemaTypeIdsWithChildren)3010 TEST_F(SchemaStoreTest, DiamondGetSchemaTypeIdsWithChildren) {
3011 ICING_ASSERT_OK_AND_ASSIGN(
3012 std::unique_ptr<SchemaStore> schema_store,
3013 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3014 feature_flags_.get()));
3015
3016 // Create a schema with the following inheritance relation:
3017 // A
3018 // / \
3019 // B E
3020 // / \ /
3021 // C D
3022 // \ /
3023 // F
3024 SchemaTypeConfigProto type_a = SchemaTypeConfigBuilder().SetType("A").Build();
3025 SchemaTypeConfigProto type_b =
3026 SchemaTypeConfigBuilder().SetType("B").AddParentType("A").Build();
3027 SchemaTypeConfigProto type_c =
3028 SchemaTypeConfigBuilder().SetType("C").AddParentType("B").Build();
3029 SchemaTypeConfigProto type_d = SchemaTypeConfigBuilder()
3030 .SetType("D")
3031 .AddParentType("B")
3032 .AddParentType("E")
3033 .Build();
3034 SchemaTypeConfigProto type_e =
3035 SchemaTypeConfigBuilder().SetType("E").AddParentType("A").Build();
3036 SchemaTypeConfigProto type_f = SchemaTypeConfigBuilder()
3037 .SetType("F")
3038 .AddParentType("C")
3039 .AddParentType("D")
3040 .Build();
3041 SchemaProto schema = SchemaBuilder()
3042 .AddType(type_a)
3043 .AddType(type_b)
3044 .AddType(type_c)
3045 .AddType(type_d)
3046 .AddType(type_e)
3047 .AddType(type_f)
3048 .Build();
3049 ICING_ASSERT_OK(schema_store->SetSchema(
3050 schema, /*ignore_errors_and_delete_documents=*/false));
3051
3052 // Get schema type id for each type.
3053 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_a_id,
3054 schema_store->GetSchemaTypeId("A"));
3055 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_b_id,
3056 schema_store->GetSchemaTypeId("B"));
3057 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_c_id,
3058 schema_store->GetSchemaTypeId("C"));
3059 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_d_id,
3060 schema_store->GetSchemaTypeId("D"));
3061 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_e_id,
3062 schema_store->GetSchemaTypeId("E"));
3063 ICING_ASSERT_OK_AND_ASSIGN(SchemaTypeId type_f_id,
3064 schema_store->GetSchemaTypeId("F"));
3065
3066 // Check the results from GetSchemaTypeIdsWithChildren
3067 EXPECT_THAT(
3068 schema_store->GetSchemaTypeIdsWithChildren("A"),
3069 IsOkAndHolds(Pointee(UnorderedElementsAre(
3070 type_a_id, type_b_id, type_c_id, type_d_id, type_e_id, type_f_id))));
3071 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("B"),
3072 IsOkAndHolds(Pointee(UnorderedElementsAre(
3073 type_b_id, type_c_id, type_d_id, type_f_id))));
3074 EXPECT_THAT(
3075 schema_store->GetSchemaTypeIdsWithChildren("C"),
3076 IsOkAndHolds(Pointee(UnorderedElementsAre(type_c_id, type_f_id))));
3077 EXPECT_THAT(
3078 schema_store->GetSchemaTypeIdsWithChildren("D"),
3079 IsOkAndHolds(Pointee(UnorderedElementsAre(type_d_id, type_f_id))));
3080 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("E"),
3081 IsOkAndHolds(Pointee(
3082 UnorderedElementsAre(type_e_id, type_d_id, type_f_id))));
3083 EXPECT_THAT(schema_store->GetSchemaTypeIdsWithChildren("F"),
3084 IsOkAndHolds(Pointee(UnorderedElementsAre(type_f_id))));
3085 }
3086
TEST_F(SchemaStoreTest,IndexableFieldsAreDefined)3087 TEST_F(SchemaStoreTest, IndexableFieldsAreDefined) {
3088 SchemaTypeConfigProto email_type =
3089 SchemaTypeConfigBuilder()
3090 .SetType("Email")
3091 .AddProperty(
3092 PropertyConfigBuilder()
3093 .SetName("subject")
3094 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3095 .SetCardinality(CARDINALITY_REQUIRED))
3096 .AddProperty(
3097 PropertyConfigBuilder()
3098 .SetName("senderQualifiedId")
3099 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3100 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3101 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3102 .SetCardinality(CARDINALITY_REQUIRED))
3103 .AddProperty(PropertyConfigBuilder()
3104 .SetName("recipients")
3105 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
3106 .SetCardinality(CARDINALITY_REPEATED))
3107 .AddProperty(PropertyConfigBuilder()
3108 .SetName("recipientIds")
3109 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3110 .SetCardinality(CARDINALITY_REPEATED))
3111 .AddProperty(PropertyConfigBuilder()
3112 .SetName("timestamp")
3113 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3114 .SetCardinality(CARDINALITY_REQUIRED))
3115 .Build();
3116
3117 SchemaProto schema = SchemaBuilder().AddType(email_type).Build();
3118 ICING_ASSERT_OK_AND_ASSIGN(
3119 std::unique_ptr<SchemaStore> schema_store,
3120 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3121 feature_flags_.get()));
3122 ICING_ASSERT_OK(schema_store->SetSchema(
3123 schema, /*ignore_errors_and_delete_documents=*/false));
3124 constexpr SchemaTypeId kTypeEmailSchemaId = 0;
3125
3126 // Indexables.
3127 EXPECT_TRUE(
3128 schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId, "subject"));
3129 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3130 "senderQualifiedId"));
3131 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3132 "recipients"));
3133 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3134 "recipientIds"));
3135 EXPECT_TRUE(
3136 schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId, "timestamp"));
3137 }
3138
TEST_F(SchemaStoreTest,JoinableFieldsAreDefined)3139 TEST_F(SchemaStoreTest, JoinableFieldsAreDefined) {
3140 SchemaTypeConfigProto email_type =
3141 SchemaTypeConfigBuilder()
3142 .SetType("Email")
3143 .AddProperty(PropertyConfigBuilder()
3144 .SetName("tagQualifiedId")
3145 .SetDataType(TYPE_STRING)
3146 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3147 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3148 .SetCardinality(CARDINALITY_REQUIRED))
3149 .AddProperty(
3150 PropertyConfigBuilder()
3151 .SetName("senderQualifiedId")
3152 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3153 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3154 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3155 .SetCardinality(CARDINALITY_REQUIRED))
3156 .Build();
3157
3158 SchemaProto schema = SchemaBuilder().AddType(email_type).Build();
3159 ICING_ASSERT_OK_AND_ASSIGN(
3160 std::unique_ptr<SchemaStore> schema_store,
3161 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3162 feature_flags_.get()));
3163 ICING_ASSERT_OK(schema_store->SetSchema(
3164 schema, /*ignore_errors_and_delete_documents=*/false));
3165 constexpr SchemaTypeId kTypeEmailSchemaId = 0;
3166
3167 // Joinables.
3168 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3169 "tagQualifiedId"));
3170 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3171 "senderQualifiedId"));
3172 }
3173
TEST_F(SchemaStoreTest,NonIndexableFieldsAreDefined)3174 TEST_F(SchemaStoreTest, NonIndexableFieldsAreDefined) {
3175 SchemaTypeConfigProto email_type =
3176 SchemaTypeConfigBuilder()
3177 .SetType("Email")
3178 .AddProperty(
3179 PropertyConfigBuilder()
3180 .SetName("text")
3181 .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
3182 .SetCardinality(CARDINALITY_OPTIONAL))
3183 .AddProperty(PropertyConfigBuilder()
3184 .SetName("attachment")
3185 .SetDataType(TYPE_BYTES)
3186 .SetCardinality(CARDINALITY_REQUIRED))
3187 .AddProperty(PropertyConfigBuilder()
3188 .SetName("nonindexableInteger")
3189 .SetDataType(TYPE_INT64)
3190 .SetCardinality(CARDINALITY_REQUIRED))
3191 .Build();
3192
3193 SchemaProto schema = SchemaBuilder().AddType(email_type).Build();
3194 ICING_ASSERT_OK_AND_ASSIGN(
3195 std::unique_ptr<SchemaStore> schema_store,
3196 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3197 feature_flags_.get()));
3198 ICING_ASSERT_OK(schema_store->SetSchema(
3199 schema, /*ignore_errors_and_delete_documents=*/false));
3200 constexpr SchemaTypeId kTypeEmailSchemaId = 0;
3201
3202 // Non-indexables.
3203 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3204 "attachment"));
3205 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3206 "nonindexableInteger"));
3207 EXPECT_TRUE(
3208 schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId, "text"));
3209 }
3210
TEST_F(SchemaStoreTest,NonExistentFieldsAreUndefined)3211 TEST_F(SchemaStoreTest, NonExistentFieldsAreUndefined) {
3212 SchemaTypeConfigProto email_type =
3213 SchemaTypeConfigBuilder()
3214 .SetType("Email")
3215 .AddProperty(
3216 PropertyConfigBuilder()
3217 .SetName("subject")
3218 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3219 .SetCardinality(CARDINALITY_REQUIRED))
3220 .AddProperty(
3221 PropertyConfigBuilder()
3222 .SetName("senderQualifiedId")
3223 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3224 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3225 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3226 .SetCardinality(CARDINALITY_REQUIRED))
3227 .AddProperty(PropertyConfigBuilder()
3228 .SetName("timestamp")
3229 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3230 .SetCardinality(CARDINALITY_REQUIRED))
3231 .AddProperty(PropertyConfigBuilder()
3232 .SetName("nonindexableInteger")
3233 .SetDataType(TYPE_INT64)
3234 .SetCardinality(CARDINALITY_REQUIRED))
3235 .Build();
3236
3237 SchemaProto schema = SchemaBuilder().AddType(email_type).Build();
3238 ICING_ASSERT_OK_AND_ASSIGN(
3239 std::unique_ptr<SchemaStore> schema_store,
3240 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3241 feature_flags_.get()));
3242 ICING_ASSERT_OK(schema_store->SetSchema(
3243 schema, /*ignore_errors_and_delete_documents=*/false));
3244 constexpr SchemaTypeId kTypeEmailSchemaId = 0;
3245
3246 // Non-existents.
3247 EXPECT_FALSE(
3248 schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId, "foobar"));
3249 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId,
3250 "timestamp.foo"));
3251 EXPECT_FALSE(
3252 schema_store->IsPropertyDefinedInSchema(kTypeEmailSchemaId, "time"));
3253 }
3254
TEST_F(SchemaStoreTest,NestedIndexableFieldsAreDefined)3255 TEST_F(SchemaStoreTest, NestedIndexableFieldsAreDefined) {
3256 SchemaTypeConfigProto email_type =
3257 SchemaTypeConfigBuilder()
3258 .SetType("Email")
3259 .AddProperty(PropertyConfigBuilder()
3260 .SetName("tagQualifiedId")
3261 .SetDataType(TYPE_STRING)
3262 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3263 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3264 .SetCardinality(CARDINALITY_REQUIRED))
3265 .AddProperty(
3266 PropertyConfigBuilder()
3267 .SetName("subject")
3268 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3269 .SetCardinality(CARDINALITY_REQUIRED))
3270 .AddProperty(
3271 PropertyConfigBuilder()
3272 .SetName("text")
3273 .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
3274 .SetCardinality(CARDINALITY_OPTIONAL))
3275 .AddProperty(PropertyConfigBuilder()
3276 .SetName("timestamp")
3277 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3278 .SetCardinality(CARDINALITY_REQUIRED))
3279 .Build();
3280
3281 SchemaTypeConfigProto conversation_type =
3282 SchemaTypeConfigBuilder()
3283 .SetType("Conversation")
3284 .AddProperty(PropertyConfigBuilder()
3285 .SetName("emails")
3286 .SetDataTypeDocument(
3287 "Email", /*index_nested_properties=*/true)
3288 .SetCardinality(CARDINALITY_OPTIONAL))
3289 .AddProperty(
3290 PropertyConfigBuilder()
3291 .SetName("nestedNonIndexable")
3292 .SetDataTypeDocument("Email",
3293 /*index_nested_properties=*/false)
3294 .SetCardinality(CARDINALITY_OPTIONAL))
3295 .Build();
3296 SchemaProto schema =
3297 SchemaBuilder().AddType(email_type).AddType(conversation_type).Build();
3298 ICING_ASSERT_OK_AND_ASSIGN(
3299 std::unique_ptr<SchemaStore> schema_store,
3300 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3301 feature_flags_.get()));
3302 ICING_ASSERT_OK(schema_store->SetSchema(
3303 schema, /*ignore_errors_and_delete_documents=*/false));
3304 constexpr SchemaTypeId kTypeConversationSchemaId = 1;
3305
3306 // Indexables.
3307 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeConversationSchemaId,
3308 "emails.subject"));
3309 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeConversationSchemaId,
3310 "emails.timestamp"));
3311 }
3312
TEST_F(SchemaStoreTest,NestedJoinableFieldsAreDefined)3313 TEST_F(SchemaStoreTest, NestedJoinableFieldsAreDefined) {
3314 SchemaTypeConfigProto email_type =
3315 SchemaTypeConfigBuilder()
3316 .SetType("Email")
3317 .AddProperty(PropertyConfigBuilder()
3318 .SetName("tagQualifiedId")
3319 .SetDataType(TYPE_STRING)
3320 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3321 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3322 .SetCardinality(CARDINALITY_REQUIRED))
3323 .AddProperty(
3324 PropertyConfigBuilder()
3325 .SetName("subject")
3326 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3327 .SetCardinality(CARDINALITY_REQUIRED))
3328 .AddProperty(
3329 PropertyConfigBuilder()
3330 .SetName("text")
3331 .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
3332 .SetCardinality(CARDINALITY_OPTIONAL))
3333 .AddProperty(PropertyConfigBuilder()
3334 .SetName("timestamp")
3335 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3336 .SetCardinality(CARDINALITY_REQUIRED))
3337 .Build();
3338
3339 SchemaTypeConfigProto conversation_type =
3340 SchemaTypeConfigBuilder()
3341 .SetType("Conversation")
3342 .AddProperty(PropertyConfigBuilder()
3343 .SetName("emails")
3344 .SetDataTypeDocument(
3345 "Email", /*index_nested_properties=*/true)
3346 .SetCardinality(CARDINALITY_OPTIONAL))
3347 .AddProperty(
3348 PropertyConfigBuilder()
3349 .SetName("nestedNonIndexable")
3350 .SetDataTypeDocument("Email",
3351 /*index_nested_properties=*/false)
3352 .SetCardinality(CARDINALITY_OPTIONAL))
3353 .Build();
3354 SchemaProto schema =
3355 SchemaBuilder().AddType(email_type).AddType(conversation_type).Build();
3356 ICING_ASSERT_OK_AND_ASSIGN(
3357 std::unique_ptr<SchemaStore> schema_store,
3358 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3359 feature_flags_.get()));
3360 ICING_ASSERT_OK(schema_store->SetSchema(
3361 schema, /*ignore_errors_and_delete_documents=*/false));
3362 constexpr SchemaTypeId kTypeConversationSchemaId = 1;
3363
3364 // Joinables.
3365 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeConversationSchemaId,
3366 "emails.tagQualifiedId"));
3367 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(
3368 kTypeConversationSchemaId, "nestedNonIndexable.tagQualifiedId"));
3369 }
3370
TEST_F(SchemaStoreTest,NestedNonIndexableFieldsAreDefined)3371 TEST_F(SchemaStoreTest, NestedNonIndexableFieldsAreDefined) {
3372 SchemaTypeConfigProto email_type =
3373 SchemaTypeConfigBuilder()
3374 .SetType("Email")
3375 .AddProperty(PropertyConfigBuilder()
3376 .SetName("tagQualifiedId")
3377 .SetDataType(TYPE_STRING)
3378 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3379 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3380 .SetCardinality(CARDINALITY_REQUIRED))
3381 .AddProperty(
3382 PropertyConfigBuilder()
3383 .SetName("subject")
3384 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3385 .SetCardinality(CARDINALITY_REQUIRED))
3386 .AddProperty(
3387 PropertyConfigBuilder()
3388 .SetName("text")
3389 .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
3390 .SetCardinality(CARDINALITY_OPTIONAL))
3391 .AddProperty(PropertyConfigBuilder()
3392 .SetName("timestamp")
3393 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3394 .SetCardinality(CARDINALITY_REQUIRED))
3395 .Build();
3396
3397 SchemaTypeConfigProto conversation_type =
3398 SchemaTypeConfigBuilder()
3399 .SetType("Conversation")
3400 .AddProperty(PropertyConfigBuilder()
3401 .SetName("emails")
3402 .SetDataTypeDocument(
3403 "Email", /*index_nested_properties=*/true)
3404 .SetCardinality(CARDINALITY_OPTIONAL))
3405 .AddProperty(
3406 PropertyConfigBuilder()
3407 .SetName("nestedNonIndexable")
3408 .SetDataTypeDocument("Email",
3409 /*index_nested_properties=*/false)
3410 .SetCardinality(CARDINALITY_OPTIONAL))
3411 .Build();
3412 SchemaProto schema =
3413 SchemaBuilder().AddType(email_type).AddType(conversation_type).Build();
3414 ICING_ASSERT_OK_AND_ASSIGN(
3415 std::unique_ptr<SchemaStore> schema_store,
3416 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3417 feature_flags_.get()));
3418 ICING_ASSERT_OK(schema_store->SetSchema(
3419 schema, /*ignore_errors_and_delete_documents=*/false));
3420 constexpr SchemaTypeId kTypeConversationSchemaId = 1;
3421
3422 // Non-indexables.
3423 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeConversationSchemaId,
3424 "emails.text"));
3425 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(
3426 kTypeConversationSchemaId, "nestedNonIndexable.subject"));
3427 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(
3428 kTypeConversationSchemaId, "nestedNonIndexable.text"));
3429 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(
3430 kTypeConversationSchemaId, "nestedNonIndexable.timestamp"));
3431 }
3432
TEST_F(SchemaStoreTest,NestedNonExistentFieldsAreUndefined)3433 TEST_F(SchemaStoreTest, NestedNonExistentFieldsAreUndefined) {
3434 SchemaTypeConfigProto email_type =
3435 SchemaTypeConfigBuilder()
3436 .SetType("Email")
3437 .AddProperty(PropertyConfigBuilder()
3438 .SetName("tagQualifiedId")
3439 .SetDataType(TYPE_STRING)
3440 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3441 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3442 .SetCardinality(CARDINALITY_REQUIRED))
3443 .AddProperty(
3444 PropertyConfigBuilder()
3445 .SetName("subject")
3446 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3447 .SetCardinality(CARDINALITY_REQUIRED))
3448 .AddProperty(
3449 PropertyConfigBuilder()
3450 .SetName("text")
3451 .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
3452 .SetCardinality(CARDINALITY_OPTIONAL))
3453 .AddProperty(PropertyConfigBuilder()
3454 .SetName("timestamp")
3455 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3456 .SetCardinality(CARDINALITY_REQUIRED))
3457 .Build();
3458
3459 SchemaTypeConfigProto conversation_type =
3460 SchemaTypeConfigBuilder()
3461 .SetType("Conversation")
3462 .AddProperty(PropertyConfigBuilder()
3463 .SetName("emails")
3464 .SetDataTypeDocument(
3465 "Email", /*index_nested_properties=*/true)
3466 .SetCardinality(CARDINALITY_OPTIONAL))
3467 .AddProperty(
3468 PropertyConfigBuilder()
3469 .SetName("nestedNonIndexable")
3470 .SetDataTypeDocument("Email",
3471 /*index_nested_properties=*/false)
3472 .SetCardinality(CARDINALITY_OPTIONAL))
3473 .Build();
3474 SchemaProto schema =
3475 SchemaBuilder().AddType(email_type).AddType(conversation_type).Build();
3476 ICING_ASSERT_OK_AND_ASSIGN(
3477 std::unique_ptr<SchemaStore> schema_store,
3478 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3479 feature_flags_.get()));
3480 ICING_ASSERT_OK(schema_store->SetSchema(
3481 schema, /*ignore_errors_and_delete_documents=*/false));
3482 constexpr SchemaTypeId kTypeConversationSchemaId = 1;
3483
3484 // Non-existents.
3485 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(
3486 kTypeConversationSchemaId, "emails.foobar"));
3487 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(
3488 kTypeConversationSchemaId, "nestedNonIndexable.foobar"));
3489 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(
3490 kTypeConversationSchemaId, "emails.timestamp.foo"));
3491 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(
3492 kTypeConversationSchemaId, "emails.time"));
3493 }
3494
TEST_F(SchemaStoreTest,IntermediateDocumentPropertiesAreDefined)3495 TEST_F(SchemaStoreTest, IntermediateDocumentPropertiesAreDefined) {
3496 SchemaTypeConfigProto email_type =
3497 SchemaTypeConfigBuilder()
3498 .SetType("Email")
3499 .AddProperty(PropertyConfigBuilder()
3500 .SetName("tagQualifiedId")
3501 .SetDataType(TYPE_STRING)
3502 .SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID,
3503 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
3504 .SetCardinality(CARDINALITY_REQUIRED))
3505 .AddProperty(
3506 PropertyConfigBuilder()
3507 .SetName("subject")
3508 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3509 .SetCardinality(CARDINALITY_REQUIRED))
3510 .AddProperty(
3511 PropertyConfigBuilder()
3512 .SetName("text")
3513 .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
3514 .SetCardinality(CARDINALITY_OPTIONAL))
3515 .AddProperty(PropertyConfigBuilder()
3516 .SetName("timestamp")
3517 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
3518 .SetCardinality(CARDINALITY_REQUIRED))
3519 .Build();
3520
3521 SchemaTypeConfigProto conversation_type =
3522 SchemaTypeConfigBuilder()
3523 .SetType("Conversation")
3524 .AddProperty(PropertyConfigBuilder()
3525 .SetName("emails")
3526 .SetDataTypeDocument(
3527 "Email", /*index_nested_properties=*/true)
3528 .SetCardinality(CARDINALITY_OPTIONAL))
3529 .AddProperty(
3530 PropertyConfigBuilder()
3531 .SetName("nestedNonIndexable")
3532 .SetDataTypeDocument("Email",
3533 /*index_nested_properties=*/false)
3534 .SetCardinality(CARDINALITY_OPTIONAL))
3535 .Build();
3536 SchemaProto schema =
3537 SchemaBuilder().AddType(email_type).AddType(conversation_type).Build();
3538 ICING_ASSERT_OK_AND_ASSIGN(
3539 std::unique_ptr<SchemaStore> schema_store,
3540 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3541 feature_flags_.get()));
3542 ICING_ASSERT_OK(schema_store->SetSchema(
3543 schema, /*ignore_errors_and_delete_documents=*/false));
3544 constexpr SchemaTypeId kTypeConversationSchemaId = 1;
3545
3546 // Intermediate documents props.
3547 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeConversationSchemaId,
3548 "emails"));
3549 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeConversationSchemaId,
3550 "nestedNonIndexable"));
3551 }
3552
TEST_F(SchemaStoreTest,CyclePathsAreDefined)3553 TEST_F(SchemaStoreTest, CyclePathsAreDefined) {
3554 SchemaTypeConfigProto type_a =
3555 SchemaTypeConfigBuilder()
3556 .SetType("A")
3557 .AddProperty(
3558 PropertyConfigBuilder()
3559 .SetName("subject")
3560 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3561 .SetCardinality(CARDINALITY_REQUIRED))
3562 .AddProperty(
3563 PropertyConfigBuilder()
3564 .SetName("b")
3565 .SetDataTypeDocument("B", /*index_nested_properties=*/true)
3566 .SetCardinality(CARDINALITY_OPTIONAL))
3567 .Build();
3568
3569 SchemaTypeConfigProto type_b =
3570 SchemaTypeConfigBuilder()
3571 .SetType("B")
3572 .AddProperty(
3573 PropertyConfigBuilder()
3574 .SetName("body")
3575 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3576 .SetCardinality(CARDINALITY_REQUIRED))
3577 .AddProperty(
3578 PropertyConfigBuilder()
3579 .SetName("a")
3580 .SetDataTypeDocument("A", /*index_nested_properties=*/false)
3581 .SetCardinality(CARDINALITY_OPTIONAL))
3582 .Build();
3583 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3584 ICING_ASSERT_OK_AND_ASSIGN(
3585 std::unique_ptr<SchemaStore> schema_store,
3586 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3587 feature_flags_.get()));
3588 ICING_ASSERT_OK(schema_store->SetSchema(
3589 schema, /*ignore_errors_and_delete_documents=*/false));
3590 constexpr SchemaTypeId kTypeASchemaId = 0;
3591 constexpr SchemaTypeId kTypeBSchemaId = 1;
3592
3593 // A's top-level properties
3594 EXPECT_TRUE(
3595 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "subject"));
3596 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b"));
3597
3598 // A's nested properties in B
3599 EXPECT_TRUE(
3600 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.body"));
3601 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.a"));
3602
3603 // A's nested properties in B's nested property in A
3604 EXPECT_TRUE(
3605 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.a.subject"));
3606 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.a.b"));
3607
3608 // B's top-level properties
3609 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "body"));
3610 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a"));
3611
3612 // B's nested properties in A
3613 EXPECT_TRUE(
3614 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.subject"));
3615 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.b"));
3616
3617 // B's nested properties in A's nested property in B
3618 EXPECT_TRUE(
3619 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.b.body"));
3620 EXPECT_TRUE(schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.b.a"));
3621 }
3622
TEST_F(SchemaStoreTest,WrongTypeCyclePathsAreUndefined)3623 TEST_F(SchemaStoreTest, WrongTypeCyclePathsAreUndefined) {
3624 SchemaTypeConfigProto type_a =
3625 SchemaTypeConfigBuilder()
3626 .SetType("A")
3627 .AddProperty(
3628 PropertyConfigBuilder()
3629 .SetName("subject")
3630 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3631 .SetCardinality(CARDINALITY_REQUIRED))
3632 .AddProperty(
3633 PropertyConfigBuilder()
3634 .SetName("b")
3635 .SetDataTypeDocument("B", /*index_nested_properties=*/true)
3636 .SetCardinality(CARDINALITY_OPTIONAL))
3637 .Build();
3638
3639 SchemaTypeConfigProto type_b =
3640 SchemaTypeConfigBuilder()
3641 .SetType("B")
3642 .AddProperty(
3643 PropertyConfigBuilder()
3644 .SetName("body")
3645 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3646 .SetCardinality(CARDINALITY_REQUIRED))
3647 .AddProperty(
3648 PropertyConfigBuilder()
3649 .SetName("a")
3650 .SetDataTypeDocument("A", /*index_nested_properties=*/false)
3651 .SetCardinality(CARDINALITY_OPTIONAL))
3652 .Build();
3653 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3654 ICING_ASSERT_OK_AND_ASSIGN(
3655 std::unique_ptr<SchemaStore> schema_store,
3656 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3657 feature_flags_.get()));
3658 ICING_ASSERT_OK(schema_store->SetSchema(
3659 schema, /*ignore_errors_and_delete_documents=*/false));
3660 constexpr SchemaTypeId kTypeASchemaId = 0;
3661 constexpr SchemaTypeId kTypeBSchemaId = 1;
3662
3663 // The same paths as above, but we check the wrong types instead.
3664 // A's top-level properties
3665 EXPECT_FALSE(
3666 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "subject"));
3667 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "b"));
3668
3669 // A's nested properties in B
3670 EXPECT_FALSE(
3671 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "b.body"));
3672 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "b.a"));
3673
3674 // A's nested properties in B's nested property in A
3675 EXPECT_FALSE(
3676 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "b.a.subject"));
3677 EXPECT_FALSE(
3678 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "b.a.b"));
3679
3680 // B's top-level properties
3681 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "body"));
3682 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "a"));
3683
3684 // B's nested properties in A
3685 EXPECT_FALSE(
3686 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "a.subject"));
3687 EXPECT_FALSE(schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "a.b"));
3688
3689 // B's nested properties in A's nested property in B
3690 EXPECT_FALSE(
3691 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "a.b.body"));
3692 EXPECT_FALSE(
3693 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "a.b.a"));
3694 }
3695
TEST_F(SchemaStoreTest,CyclePathsNonexistentPropertiesAreUndefined)3696 TEST_F(SchemaStoreTest, CyclePathsNonexistentPropertiesAreUndefined) {
3697 SchemaTypeConfigProto type_a =
3698 SchemaTypeConfigBuilder()
3699 .SetType("A")
3700 .AddProperty(
3701 PropertyConfigBuilder()
3702 .SetName("subject")
3703 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3704 .SetCardinality(CARDINALITY_REQUIRED))
3705 .AddProperty(
3706 PropertyConfigBuilder()
3707 .SetName("b")
3708 .SetDataTypeDocument("B", /*index_nested_properties=*/true)
3709 .SetCardinality(CARDINALITY_OPTIONAL))
3710 .Build();
3711
3712 SchemaTypeConfigProto type_b =
3713 SchemaTypeConfigBuilder()
3714 .SetType("B")
3715 .AddProperty(
3716 PropertyConfigBuilder()
3717 .SetName("body")
3718 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
3719 .SetCardinality(CARDINALITY_REQUIRED))
3720 .AddProperty(
3721 PropertyConfigBuilder()
3722 .SetName("a")
3723 .SetDataTypeDocument("A", /*index_nested_properties=*/false)
3724 .SetCardinality(CARDINALITY_OPTIONAL))
3725 .Build();
3726 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3727 ICING_ASSERT_OK_AND_ASSIGN(
3728 std::unique_ptr<SchemaStore> schema_store,
3729 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3730 feature_flags_.get()));
3731 ICING_ASSERT_OK(schema_store->SetSchema(
3732 schema, /*ignore_errors_and_delete_documents=*/false));
3733 constexpr SchemaTypeId kTypeASchemaId = 0;
3734 constexpr SchemaTypeId kTypeBSchemaId = 1;
3735
3736 // Undefined paths in A
3737 EXPECT_FALSE(
3738 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.subject"));
3739 EXPECT_FALSE(
3740 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.a.body"));
3741 EXPECT_FALSE(
3742 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.a.a"));
3743 EXPECT_FALSE(
3744 schema_store->IsPropertyDefinedInSchema(kTypeASchemaId, "b.a.subject.b"));
3745
3746 // Undefined paths in B
3747 EXPECT_FALSE(
3748 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.body"));
3749 EXPECT_FALSE(
3750 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.b.subject"));
3751 EXPECT_FALSE(
3752 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.b.b"));
3753 EXPECT_FALSE(
3754 schema_store->IsPropertyDefinedInSchema(kTypeBSchemaId, "a.b.body.a"));
3755 }
3756
TEST_F(SchemaStoreTest,LoadsOverlaySchemaOnInit)3757 TEST_F(SchemaStoreTest, LoadsOverlaySchemaOnInit) {
3758 // Create a schema that is rollback incompatible and will trigger us to create
3759 // an overlay schema.
3760 PropertyConfigBuilder indexed_string_property_builder =
3761 PropertyConfigBuilder()
3762 .SetCardinality(CARDINALITY_OPTIONAL)
3763 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN);
3764 SchemaTypeConfigProto type_a =
3765 SchemaTypeConfigBuilder()
3766 .SetType("type_a")
3767 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3768 .AddProperty(
3769 PropertyConfigBuilder()
3770 .SetName("propRfc")
3771 .SetCardinality(CARDINALITY_OPTIONAL)
3772 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
3773 .Build();
3774 SchemaTypeConfigProto type_b =
3775 SchemaTypeConfigBuilder()
3776 .SetType("type_b")
3777 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3778 .Build();
3779 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3780
3781 {
3782 // Create an instance of the schema store and set the schema.
3783 ICING_ASSERT_OK_AND_ASSIGN(
3784 std::unique_ptr<SchemaStore> schema_store,
3785 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3786 feature_flags_.get()));
3787 ICING_ASSERT_OK(schema_store->SetSchema(
3788 schema, /*ignore_errors_and_delete_documents=*/false));
3789
3790 EXPECT_THAT(schema_store->GetSchema(),
3791 IsOkAndHolds(Pointee(EqualsProto(schema))));
3792 }
3793
3794 {
3795 // Create a new of the schema store and check that the same schema is
3796 // present.
3797 ICING_ASSERT_OK_AND_ASSIGN(
3798 std::unique_ptr<SchemaStore> schema_store,
3799 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3800 feature_flags_.get()));
3801 EXPECT_THAT(schema_store->GetSchema(),
3802 IsOkAndHolds(Pointee(EqualsProto(schema))));
3803
3804 // The overlay should exist
3805 std::string overlay_schema_path = schema_store_dir_ + "/overlay_schema.pb";
3806 ASSERT_TRUE(filesystem_.FileExists(overlay_schema_path.c_str()));
3807
3808 // The base schema should hold a compatible schema
3809 SchemaTypeConfigProto modified_type_a =
3810 SchemaTypeConfigBuilder()
3811 .SetType("type_a")
3812 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3813 .AddProperty(PropertyConfigBuilder()
3814 .SetName("propRfc")
3815 .SetCardinality(CARDINALITY_OPTIONAL)
3816 .SetDataType(TYPE_STRING))
3817 .Build();
3818 SchemaProto expected_base_schema =
3819 SchemaBuilder().AddType(modified_type_a).AddType(type_b).Build();
3820 std::string base_schema_path = schema_store_dir_ + "/schema.pb";
3821 auto base_schema_file_ = std::make_unique<FileBackedProto<SchemaProto>>(
3822 filesystem_, base_schema_path);
3823 ICING_ASSERT_OK_AND_ASSIGN(const SchemaProto* base_schema,
3824 base_schema_file_->Read());
3825 EXPECT_THAT(*base_schema, EqualsProto(expected_base_schema));
3826 }
3827 }
3828
TEST_F(SchemaStoreTest,LoadsBaseSchemaWithNoOverlayOnInit)3829 TEST_F(SchemaStoreTest, LoadsBaseSchemaWithNoOverlayOnInit) {
3830 // Create a normal schema that won't require an overlay.
3831 PropertyConfigBuilder indexed_string_property_builder =
3832 PropertyConfigBuilder()
3833 .SetCardinality(CARDINALITY_OPTIONAL)
3834 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN);
3835 SchemaTypeConfigProto type_a =
3836 SchemaTypeConfigBuilder()
3837 .SetType("type_a")
3838 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3839 .AddProperty(
3840 PropertyConfigBuilder()
3841 .SetName("propRfc")
3842 .SetCardinality(CARDINALITY_OPTIONAL)
3843 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN))
3844 .Build();
3845 SchemaTypeConfigProto type_b =
3846 SchemaTypeConfigBuilder()
3847 .SetType("type_b")
3848 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3849 .Build();
3850 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3851
3852 {
3853 // Create an instance of the schema store and set the schema.
3854 ICING_ASSERT_OK_AND_ASSIGN(
3855 std::unique_ptr<SchemaStore> schema_store,
3856 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3857 feature_flags_.get()));
3858 ICING_ASSERT_OK(schema_store->SetSchema(
3859 schema, /*ignore_errors_and_delete_documents=*/false));
3860
3861 EXPECT_THAT(schema_store->GetSchema(),
3862 IsOkAndHolds(Pointee(EqualsProto(schema))));
3863 }
3864
3865 {
3866 // Create a new instance of the schema store and check that the same schema
3867 // is present.
3868 ICING_ASSERT_OK_AND_ASSIGN(
3869 std::unique_ptr<SchemaStore> schema_store,
3870 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3871 feature_flags_.get()));
3872 EXPECT_THAT(schema_store->GetSchema(),
3873 IsOkAndHolds(Pointee(EqualsProto(schema))));
3874
3875 // Additionally, the overlay should not exist
3876 std::string overlay_schema_path = schema_store_dir_ + "/overlay_schema.pb";
3877 ASSERT_FALSE(filesystem_.FileExists(overlay_schema_path.c_str()));
3878 }
3879 }
3880
TEST_F(SchemaStoreTest,LoadSchemaBackupSchemaMissing)3881 TEST_F(SchemaStoreTest, LoadSchemaBackupSchemaMissing) {
3882 // Create a schema that is rollback incompatible and will trigger us to create
3883 // a backup schema.
3884 PropertyConfigBuilder indexed_string_property_builder =
3885 PropertyConfigBuilder()
3886 .SetCardinality(CARDINALITY_OPTIONAL)
3887 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN);
3888 SchemaTypeConfigProto type_a =
3889 SchemaTypeConfigBuilder()
3890 .SetType("type_a")
3891 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3892 .AddProperty(
3893 PropertyConfigBuilder()
3894 .SetName("propRfc")
3895 .SetCardinality(CARDINALITY_OPTIONAL)
3896 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
3897 .Build();
3898 SchemaTypeConfigProto type_b =
3899 SchemaTypeConfigBuilder()
3900 .SetType("type_b")
3901 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3902 .Build();
3903 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3904
3905 {
3906 // Create an instance of the schema store and set the schema.
3907 ICING_ASSERT_OK_AND_ASSIGN(
3908 std::unique_ptr<SchemaStore> schema_store,
3909 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3910 feature_flags_.get()));
3911 ICING_ASSERT_OK(schema_store->SetSchema(
3912 schema, /*ignore_errors_and_delete_documents=*/false));
3913
3914 EXPECT_THAT(schema_store->GetSchema(),
3915 IsOkAndHolds(Pointee(EqualsProto(schema))));
3916 }
3917
3918 // Delete the backup schema.
3919 std::string backup_schema_path = schema_store_dir_ + "/schema.pb";
3920 ASSERT_TRUE(filesystem_.DeleteFile(backup_schema_path.c_str()));
3921
3922 {
3923 // Create a new instance of the schema store and check that it fails because
3924 // the backup schema is not available.
3925 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_,
3926 &fake_clock_, feature_flags_.get()),
3927 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
3928 }
3929 }
3930
TEST_F(SchemaStoreTest,LoadSchemaOverlaySchemaMissing)3931 TEST_F(SchemaStoreTest, LoadSchemaOverlaySchemaMissing) {
3932 // Create a schema that is rollback incompatible and will trigger us to create
3933 // a backup schema.
3934 PropertyConfigBuilder indexed_string_property_builder =
3935 PropertyConfigBuilder()
3936 .SetCardinality(CARDINALITY_OPTIONAL)
3937 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN);
3938 SchemaTypeConfigProto type_a =
3939 SchemaTypeConfigBuilder()
3940 .SetType("type_a")
3941 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3942 .AddProperty(
3943 PropertyConfigBuilder()
3944 .SetName("propRfc")
3945 .SetCardinality(CARDINALITY_OPTIONAL)
3946 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
3947 .Build();
3948 SchemaTypeConfigProto type_b =
3949 SchemaTypeConfigBuilder()
3950 .SetType("type_b")
3951 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3952 .Build();
3953 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
3954
3955 {
3956 // Create an instance of the schema store and set the schema.
3957 ICING_ASSERT_OK_AND_ASSIGN(
3958 std::unique_ptr<SchemaStore> schema_store,
3959 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
3960 feature_flags_.get()));
3961 ICING_ASSERT_OK(schema_store->SetSchema(
3962 schema, /*ignore_errors_and_delete_documents=*/false));
3963
3964 EXPECT_THAT(schema_store->GetSchema(),
3965 IsOkAndHolds(Pointee(EqualsProto(schema))));
3966 }
3967
3968 // Delete the overlay schema.
3969 std::string overlay_schema_path = schema_store_dir_ + "/overlay_schema.pb";
3970 ASSERT_TRUE(filesystem_.DeleteFile(overlay_schema_path.c_str()));
3971
3972 {
3973 // Create a new instance of the schema store and check that it fails because
3974 // the overlay schema is not available when we expected it to be.
3975 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_,
3976 &fake_clock_, feature_flags_.get()),
3977 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
3978 }
3979 }
3980
TEST_F(SchemaStoreTest,LoadSchemaHeaderMissing)3981 TEST_F(SchemaStoreTest, LoadSchemaHeaderMissing) {
3982 // Create a schema that is rollback incompatible and will trigger us to create
3983 // a backup schema.
3984 PropertyConfigBuilder indexed_string_property_builder =
3985 PropertyConfigBuilder()
3986 .SetCardinality(CARDINALITY_OPTIONAL)
3987 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN);
3988 SchemaTypeConfigProto type_a =
3989 SchemaTypeConfigBuilder()
3990 .SetType("type_a")
3991 .AddProperty(indexed_string_property_builder.SetName("prop0"))
3992 .AddProperty(
3993 PropertyConfigBuilder()
3994 .SetName("propRfc")
3995 .SetCardinality(CARDINALITY_OPTIONAL)
3996 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
3997 .Build();
3998 SchemaTypeConfigProto type_b =
3999 SchemaTypeConfigBuilder()
4000 .SetType("type_b")
4001 .AddProperty(indexed_string_property_builder.SetName("prop0"))
4002 .Build();
4003 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
4004
4005 {
4006 // Create an instance of the schema store and set the schema.
4007 ICING_ASSERT_OK_AND_ASSIGN(
4008 std::unique_ptr<SchemaStore> schema_store,
4009 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4010 feature_flags_.get()));
4011 ICING_ASSERT_OK(schema_store->SetSchema(
4012 schema, /*ignore_errors_and_delete_documents=*/false));
4013
4014 EXPECT_THAT(schema_store->GetSchema(),
4015 IsOkAndHolds(Pointee(EqualsProto(schema))));
4016 }
4017
4018 // Delete the overlay schema.
4019 std::string schema_header_path = schema_store_dir_ + "/schema_store_header";
4020 ASSERT_TRUE(filesystem_.DeleteFile(schema_header_path.c_str()));
4021
4022 {
4023 // Create a new of the schema store and check that the same schema is
4024 // present.
4025 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_,
4026 &fake_clock_, feature_flags_.get()),
4027 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
4028 }
4029 }
4030
TEST_F(SchemaStoreTest,LoadSchemaNoOverlayHeaderMissing)4031 TEST_F(SchemaStoreTest, LoadSchemaNoOverlayHeaderMissing) {
4032 // Create a normal schema that won't require a backup.
4033 PropertyConfigBuilder indexed_string_property_builder =
4034 PropertyConfigBuilder()
4035 .SetCardinality(CARDINALITY_OPTIONAL)
4036 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN);
4037 SchemaTypeConfigProto type_a =
4038 SchemaTypeConfigBuilder()
4039 .SetType("type_a")
4040 .AddProperty(indexed_string_property_builder.SetName("prop0"))
4041 .AddProperty(
4042 PropertyConfigBuilder()
4043 .SetName("propRfc")
4044 .SetCardinality(CARDINALITY_OPTIONAL)
4045 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN))
4046 .Build();
4047 SchemaTypeConfigProto type_b =
4048 SchemaTypeConfigBuilder()
4049 .SetType("type_b")
4050 .AddProperty(indexed_string_property_builder.SetName("prop0"))
4051 .Build();
4052 SchemaProto schema = SchemaBuilder().AddType(type_a).AddType(type_b).Build();
4053
4054 {
4055 // Create an instance of the schema store and set the schema.
4056 ICING_ASSERT_OK_AND_ASSIGN(
4057 std::unique_ptr<SchemaStore> schema_store,
4058 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4059 feature_flags_.get()));
4060 ICING_ASSERT_OK(schema_store->SetSchema(
4061 schema, /*ignore_errors_and_delete_documents=*/false));
4062
4063 EXPECT_THAT(schema_store->GetSchema(),
4064 IsOkAndHolds(Pointee(EqualsProto(schema))));
4065 }
4066
4067 // Delete the schema header.
4068 std::string schema_header_path = schema_store_dir_ + "/schema_store_header";
4069 ASSERT_TRUE(filesystem_.DeleteFile(schema_header_path.c_str()));
4070
4071 {
4072 // Create a new instance of the schema store and check that it fails because
4073 // the schema header (which is now a part of the ground truth) is not
4074 // available.
4075 EXPECT_THAT(SchemaStore::Create(&filesystem_, schema_store_dir_,
4076 &fake_clock_, feature_flags_.get()),
4077 StatusIs(libtextclassifier3::StatusCode::INTERNAL));
4078 }
4079 }
4080
TEST_F(SchemaStoreTest,MigrateSchemaCompatibleNoChange)4081 TEST_F(SchemaStoreTest, MigrateSchemaCompatibleNoChange) {
4082 // Create a schema that is rollback incompatible and will trigger us to create
4083 // a backup schema.
4084 SchemaTypeConfigProto type_a =
4085 SchemaTypeConfigBuilder()
4086 .SetType("type_a")
4087 .AddProperty(
4088 PropertyConfigBuilder()
4089 .SetName("propRfc")
4090 .SetCardinality(CARDINALITY_OPTIONAL)
4091 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4092 .Build();
4093 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4094
4095 {
4096 // Create an instance of the schema store and set the schema.
4097 ICING_ASSERT_OK_AND_ASSIGN(
4098 std::unique_ptr<SchemaStore> schema_store,
4099 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4100 feature_flags_.get()));
4101 ICING_ASSERT_OK(schema_store->SetSchema(
4102 schema, /*ignore_errors_and_delete_documents=*/false));
4103
4104 EXPECT_THAT(schema_store->GetSchema(),
4105 IsOkAndHolds(Pointee(EqualsProto(schema))));
4106 }
4107
4108 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4109 &filesystem_, schema_store_dir_, version_util::StateChange::kCompatible,
4110 version_util::kVersion, /*perform_schema_database_migration=*/false));
4111
4112 {
4113 // Create a new of the schema store and check that the same schema is
4114 // present.
4115 ICING_ASSERT_OK_AND_ASSIGN(
4116 std::unique_ptr<SchemaStore> schema_store,
4117 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4118 feature_flags_.get()));
4119 EXPECT_THAT(schema_store->GetSchema(),
4120 IsOkAndHolds(Pointee(EqualsProto(schema))));
4121 }
4122 }
4123
TEST_F(SchemaStoreTest,MigrateSchemaUpgradeNoChange)4124 TEST_F(SchemaStoreTest, MigrateSchemaUpgradeNoChange) {
4125 // Create a schema that is rollback incompatible and will trigger us to create
4126 // a backup schema.
4127 SchemaTypeConfigProto type_a =
4128 SchemaTypeConfigBuilder()
4129 .SetType("type_a")
4130 .AddProperty(
4131 PropertyConfigBuilder()
4132 .SetName("propRfc")
4133 .SetCardinality(CARDINALITY_OPTIONAL)
4134 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4135 .Build();
4136 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4137
4138 {
4139 // Create an instance of the schema store and set the schema.
4140 ICING_ASSERT_OK_AND_ASSIGN(
4141 std::unique_ptr<SchemaStore> schema_store,
4142 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4143 feature_flags_.get()));
4144 ICING_ASSERT_OK(schema_store->SetSchema(
4145 schema, /*ignore_errors_and_delete_documents=*/false));
4146
4147 EXPECT_THAT(schema_store->GetSchema(),
4148 IsOkAndHolds(Pointee(EqualsProto(schema))));
4149 }
4150
4151 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4152 &filesystem_, schema_store_dir_, version_util::StateChange::kUpgrade,
4153 version_util::kVersion + 1, /*perform_schema_database_migration=*/false));
4154
4155 {
4156 // Create a new of the schema store and check that the same schema is
4157 // present.
4158 ICING_ASSERT_OK_AND_ASSIGN(
4159 std::unique_ptr<SchemaStore> schema_store,
4160 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4161 feature_flags_.get()));
4162 EXPECT_THAT(schema_store->GetSchema(),
4163 IsOkAndHolds(Pointee(EqualsProto(schema))));
4164 }
4165 }
4166
TEST_F(SchemaStoreTest,MigrateSchemaVersionZeroUpgradeNoChange)4167 TEST_F(SchemaStoreTest, MigrateSchemaVersionZeroUpgradeNoChange) {
4168 // Because we are upgrading from version zero, the schema must be compatible
4169 // with version zero.
4170 SchemaTypeConfigProto type_a =
4171 SchemaTypeConfigBuilder()
4172 .SetType("type_a")
4173 .AddProperty(
4174 PropertyConfigBuilder()
4175 .SetName("propRfc")
4176 .SetCardinality(CARDINALITY_OPTIONAL)
4177 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN))
4178 .Build();
4179 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4180
4181 {
4182 // Create an instance of the schema store and set the schema.
4183 ICING_ASSERT_OK_AND_ASSIGN(
4184 std::unique_ptr<SchemaStore> schema_store,
4185 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4186 feature_flags_.get()));
4187 ICING_ASSERT_OK(schema_store->SetSchema(
4188 schema, /*ignore_errors_and_delete_documents=*/false));
4189
4190 EXPECT_THAT(schema_store->GetSchema(),
4191 IsOkAndHolds(Pointee(EqualsProto(schema))));
4192 }
4193
4194 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4195 &filesystem_, schema_store_dir_,
4196 version_util::StateChange::kVersionZeroUpgrade,
4197 version_util::kVersion + 1, /*perform_schema_database_migration=*/false));
4198
4199 {
4200 // Create a new of the schema store and check that the same schema is
4201 // present.
4202 ICING_ASSERT_OK_AND_ASSIGN(
4203 std::unique_ptr<SchemaStore> schema_store,
4204 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4205 feature_flags_.get()));
4206 EXPECT_THAT(schema_store->GetSchema(),
4207 IsOkAndHolds(Pointee(EqualsProto(schema))));
4208 }
4209 }
4210
TEST_F(SchemaStoreTest,MigrateSchemaRollbackDiscardsIncompatibleOverlaySchema)4211 TEST_F(SchemaStoreTest,
4212 MigrateSchemaRollbackDiscardsIncompatibleOverlaySchema) {
4213 // Because we are upgrading from version zero, the schema must be compatible
4214 // with version zero.
4215 SchemaTypeConfigProto type_a =
4216 SchemaTypeConfigBuilder()
4217 .SetType("type_a")
4218 .AddProperty(
4219 PropertyConfigBuilder()
4220 .SetName("propRfc")
4221 .SetCardinality(CARDINALITY_OPTIONAL)
4222 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4223 .Build();
4224 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4225
4226 {
4227 // Create an instance of the schema store and set the schema.
4228 ICING_ASSERT_OK_AND_ASSIGN(
4229 std::unique_ptr<SchemaStore> schema_store,
4230 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4231 feature_flags_.get()));
4232 ICING_ASSERT_OK(schema_store->SetSchema(
4233 schema, /*ignore_errors_and_delete_documents=*/false));
4234
4235 EXPECT_THAT(schema_store->GetSchema(),
4236 IsOkAndHolds(Pointee(EqualsProto(schema))));
4237 }
4238
4239 // Rollback to a version before kVersionOne. The schema header will declare
4240 // that the overlay is compatible with any version starting with kVersionOne.
4241 // So kVersionOne - 1 is incompatible and will throw out the schema.
4242 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4243 &filesystem_, schema_store_dir_, version_util::StateChange::kRollBack,
4244 version_util::kVersionOne - 1,
4245 /*perform_schema_database_migration=*/false));
4246
4247 {
4248 // Create a new of the schema store and check that we fell back to the
4249 // base schema.
4250 ICING_ASSERT_OK_AND_ASSIGN(
4251 std::unique_ptr<SchemaStore> schema_store,
4252 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4253 feature_flags_.get()));
4254
4255 SchemaTypeConfigProto other_type_a =
4256 SchemaTypeConfigBuilder()
4257 .SetType("type_a")
4258 .AddProperty(PropertyConfigBuilder()
4259 .SetName("propRfc")
4260 .SetCardinality(CARDINALITY_OPTIONAL)
4261 .SetDataType(TYPE_STRING))
4262 .Build();
4263 SchemaProto base_schema = SchemaBuilder().AddType(other_type_a).Build();
4264 EXPECT_THAT(schema_store->GetSchema(),
4265 IsOkAndHolds(Pointee(EqualsProto(base_schema))));
4266 }
4267 }
4268
TEST_F(SchemaStoreTest,MigrateSchemaRollbackKeepsCompatibleOverlaySchema)4269 TEST_F(SchemaStoreTest, MigrateSchemaRollbackKeepsCompatibleOverlaySchema) {
4270 // Because we are upgrading from version zero, the schema must be compatible
4271 // with version zero.
4272 SchemaTypeConfigProto type_a =
4273 SchemaTypeConfigBuilder()
4274 .SetType("type_a")
4275 .AddProperty(
4276 PropertyConfigBuilder()
4277 .SetName("propRfc")
4278 .SetCardinality(CARDINALITY_OPTIONAL)
4279 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4280 .Build();
4281 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4282
4283 {
4284 // Create an instance of the schema store and set the schema.
4285 ICING_ASSERT_OK_AND_ASSIGN(
4286 std::unique_ptr<SchemaStore> schema_store,
4287 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4288 feature_flags_.get()));
4289 ICING_ASSERT_OK(schema_store->SetSchema(
4290 schema, /*ignore_errors_and_delete_documents=*/false));
4291
4292 EXPECT_THAT(schema_store->GetSchema(),
4293 IsOkAndHolds(Pointee(EqualsProto(schema))));
4294 }
4295
4296 // Rollback to kVersion. The schema header will declare that the overlay is
4297 // compatible with any version starting with kVersion. So we will be
4298 // compatible and retain the overlay schema.
4299 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4300 &filesystem_, schema_store_dir_, version_util::StateChange::kRollBack,
4301 version_util::kVersion, /*perform_schema_database_migration=*/false));
4302
4303 {
4304 // Create a new of the schema store and check that the same schema is
4305 // present.
4306 ICING_ASSERT_OK_AND_ASSIGN(
4307 std::unique_ptr<SchemaStore> schema_store,
4308 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4309 feature_flags_.get()));
4310 EXPECT_THAT(schema_store->GetSchema(),
4311 IsOkAndHolds(Pointee(EqualsProto(schema))));
4312 }
4313 }
4314
TEST_F(SchemaStoreTest,MigrateSchemaRollforwardRetainsBaseSchema)4315 TEST_F(SchemaStoreTest, MigrateSchemaRollforwardRetainsBaseSchema) {
4316 SchemaTypeConfigProto type_a =
4317 SchemaTypeConfigBuilder()
4318 .SetType("type_a")
4319 .AddProperty(
4320 PropertyConfigBuilder()
4321 .SetName("propRfc")
4322 .SetCardinality(CARDINALITY_OPTIONAL)
4323 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4324 .Build();
4325 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4326 {
4327 // Create an instance of the schema store and set the schema.
4328 ICING_ASSERT_OK_AND_ASSIGN(
4329 std::unique_ptr<SchemaStore> schema_store,
4330 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4331 feature_flags_.get()));
4332 ICING_ASSERT_OK(schema_store->SetSchema(
4333 schema, /*ignore_errors_and_delete_documents=*/false));
4334
4335 EXPECT_THAT(schema_store->GetSchema(),
4336 IsOkAndHolds(Pointee(EqualsProto(schema))));
4337 }
4338
4339 // Rollback to a version before kVersionOne. The schema header will declare
4340 // that the overlay is compatible with any version starting with kVersionOne.
4341 // So kVersionOne - 1 is incompatible and will throw out the schema.
4342 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4343 &filesystem_, schema_store_dir_, version_util::StateChange::kRollBack,
4344 version_util::kVersionOne - 1,
4345 /*perform_schema_database_migration=*/false));
4346
4347 SchemaTypeConfigProto other_type_a =
4348 SchemaTypeConfigBuilder()
4349 .SetType("type_a")
4350 .AddProperty(PropertyConfigBuilder()
4351 .SetName("propRfc")
4352 .SetCardinality(CARDINALITY_OPTIONAL)
4353 .SetDataType(TYPE_STRING))
4354 .Build();
4355 SchemaProto base_schema = SchemaBuilder().AddType(other_type_a).Build();
4356
4357 {
4358 // Create a new of the schema store and check that we fell back to the
4359 // base schema.
4360 ICING_ASSERT_OK_AND_ASSIGN(
4361 std::unique_ptr<SchemaStore> schema_store,
4362 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4363 feature_flags_.get()));
4364
4365 EXPECT_THAT(schema_store->GetSchema(),
4366 IsOkAndHolds(Pointee(EqualsProto(base_schema))));
4367 }
4368
4369 // Now rollforward to a new version. This should accept whatever schema is
4370 // present (currently base schema)
4371 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4372 &filesystem_, schema_store_dir_, version_util::StateChange::kRollForward,
4373 version_util::kVersion, /*perform_schema_database_migration=*/false));
4374 {
4375 // Create a new of the schema store and check that we fell back to the
4376 // base schema.
4377 ICING_ASSERT_OK_AND_ASSIGN(
4378 std::unique_ptr<SchemaStore> schema_store,
4379 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4380 feature_flags_.get()));
4381
4382 EXPECT_THAT(schema_store->GetSchema(),
4383 IsOkAndHolds(Pointee(EqualsProto(base_schema))));
4384 }
4385 }
4386
TEST_F(SchemaStoreTest,MigrateSchemaRollforwardRetainsOverlaySchema)4387 TEST_F(SchemaStoreTest, MigrateSchemaRollforwardRetainsOverlaySchema) {
4388 SchemaTypeConfigProto type_a =
4389 SchemaTypeConfigBuilder()
4390 .SetType("type_a")
4391 .AddProperty(
4392 PropertyConfigBuilder()
4393 .SetName("propRfc")
4394 .SetCardinality(CARDINALITY_OPTIONAL)
4395 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4396 .Build();
4397 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4398 {
4399 // Create an instance of the schema store and set the schema.
4400 ICING_ASSERT_OK_AND_ASSIGN(
4401 std::unique_ptr<SchemaStore> schema_store,
4402 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4403 feature_flags_.get()));
4404 ICING_ASSERT_OK(schema_store->SetSchema(
4405 schema, /*ignore_errors_and_delete_documents=*/false));
4406
4407 EXPECT_THAT(schema_store->GetSchema(),
4408 IsOkAndHolds(Pointee(EqualsProto(schema))));
4409 }
4410
4411 // Rollback to kVersion. The schema header will declare that the overlay is
4412 // compatible with any version starting with kVersion. So we will be
4413 // compatible and retain the overlay schema.
4414 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4415 &filesystem_, schema_store_dir_, version_util::StateChange::kRollBack,
4416 version_util::kVersion, /*perform_schema_database_migration=*/false));
4417
4418 {
4419 // Create a new of the schema store and check that the same schema is
4420 // present.
4421 ICING_ASSERT_OK_AND_ASSIGN(
4422 std::unique_ptr<SchemaStore> schema_store,
4423 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4424 feature_flags_.get()));
4425
4426 EXPECT_THAT(schema_store->GetSchema(),
4427 IsOkAndHolds(Pointee(EqualsProto(schema))));
4428 }
4429
4430 // Now rollforward to a new version. This should accept whatever schema is
4431 // present (currently overlay schema)
4432 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4433 &filesystem_, schema_store_dir_, version_util::StateChange::kRollForward,
4434 version_util::kVersion, /*perform_schema_database_migration=*/false));
4435 {
4436 // Create a new of the schema store and check that the same schema is
4437 // present.
4438 ICING_ASSERT_OK_AND_ASSIGN(
4439 std::unique_ptr<SchemaStore> schema_store,
4440 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4441 feature_flags_.get()));
4442
4443 EXPECT_THAT(schema_store->GetSchema(),
4444 IsOkAndHolds(Pointee(EqualsProto(schema))));
4445 }
4446 }
4447
TEST_F(SchemaStoreTest,MigrateSchemaVersionZeroRollforwardDiscardsOverlaySchema)4448 TEST_F(SchemaStoreTest,
4449 MigrateSchemaVersionZeroRollforwardDiscardsOverlaySchema) {
4450 SchemaTypeConfigProto type_a =
4451 SchemaTypeConfigBuilder()
4452 .SetType("type_a")
4453 .AddProperty(
4454 PropertyConfigBuilder()
4455 .SetName("propRfc")
4456 .SetCardinality(CARDINALITY_OPTIONAL)
4457 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4458 .Build();
4459 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4460 {
4461 // Create an instance of the schema store and set the schema.
4462 ICING_ASSERT_OK_AND_ASSIGN(
4463 std::unique_ptr<SchemaStore> schema_store,
4464 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4465 feature_flags_.get()));
4466 ICING_ASSERT_OK(schema_store->SetSchema(
4467 schema, /*ignore_errors_and_delete_documents=*/false));
4468
4469 EXPECT_THAT(schema_store->GetSchema(),
4470 IsOkAndHolds(Pointee(EqualsProto(schema))));
4471 }
4472
4473 // A VersionZeroRollforward will always discard the overlay schema because it
4474 // could be stale.
4475 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4476 &filesystem_, schema_store_dir_,
4477 version_util::StateChange::kVersionZeroRollForward,
4478 version_util::kVersion, /*perform_schema_database_migration=*/false));
4479
4480 SchemaTypeConfigProto other_type_a =
4481 SchemaTypeConfigBuilder()
4482 .SetType("type_a")
4483 .AddProperty(PropertyConfigBuilder()
4484 .SetName("propRfc")
4485 .SetCardinality(CARDINALITY_OPTIONAL)
4486 .SetDataType(TYPE_STRING))
4487 .Build();
4488 SchemaProto base_schema = SchemaBuilder().AddType(other_type_a).Build();
4489
4490 {
4491 // Create a new of the schema store and check that we fell back to the
4492 // base schema.
4493 ICING_ASSERT_OK_AND_ASSIGN(
4494 std::unique_ptr<SchemaStore> schema_store,
4495 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4496 feature_flags_.get()));
4497
4498 EXPECT_THAT(schema_store->GetSchema(),
4499 IsOkAndHolds(Pointee(EqualsProto(base_schema))));
4500 }
4501 }
4502
TEST_F(SchemaStoreTest,MigrateSchemaVersionUndeterminedDiscardsOverlaySchema)4503 TEST_F(SchemaStoreTest, MigrateSchemaVersionUndeterminedDiscardsOverlaySchema) {
4504 SchemaTypeConfigProto type_a =
4505 SchemaTypeConfigBuilder()
4506 .SetType("type_a")
4507 .AddProperty(
4508 PropertyConfigBuilder()
4509 .SetName("propRfc")
4510 .SetCardinality(CARDINALITY_OPTIONAL)
4511 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_RFC822))
4512 .Build();
4513 SchemaProto schema = SchemaBuilder().AddType(type_a).Build();
4514 {
4515 // Create an instance of the schema store and set the schema.
4516 ICING_ASSERT_OK_AND_ASSIGN(
4517 std::unique_ptr<SchemaStore> schema_store,
4518 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4519 feature_flags_.get()));
4520 ICING_ASSERT_OK(schema_store->SetSchema(
4521 schema, /*ignore_errors_and_delete_documents=*/false));
4522
4523 EXPECT_THAT(schema_store->GetSchema(),
4524 IsOkAndHolds(Pointee(EqualsProto(schema))));
4525 }
4526
4527 // An Undetermined will always discard the overlay schema because it doesn't
4528 // know which state we're in and so it fallback to the base schema because
4529 // it should always be valid.
4530 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
4531 &filesystem_, schema_store_dir_, version_util::StateChange::kUndetermined,
4532 version_util::kVersion, /*perform_schema_database_migration=*/false));
4533
4534 SchemaTypeConfigProto other_type_a =
4535 SchemaTypeConfigBuilder()
4536 .SetType("type_a")
4537 .AddProperty(PropertyConfigBuilder()
4538 .SetName("propRfc")
4539 .SetCardinality(CARDINALITY_OPTIONAL)
4540 .SetDataType(TYPE_STRING))
4541 .Build();
4542 SchemaProto base_schema = SchemaBuilder().AddType(other_type_a).Build();
4543
4544 {
4545 // Create a new of the schema store and check that we fell back to the
4546 // base schema.
4547 ICING_ASSERT_OK_AND_ASSIGN(
4548 std::unique_ptr<SchemaStore> schema_store,
4549 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4550 feature_flags_.get()));
4551
4552 EXPECT_THAT(schema_store->GetSchema(),
4553 IsOkAndHolds(Pointee(EqualsProto(base_schema))));
4554 }
4555 }
4556
TEST_F(SchemaStoreTest,GetTypeWithBlobProperties)4557 TEST_F(SchemaStoreTest, GetTypeWithBlobProperties) {
4558 SchemaTypeConfigProto type_a =
4559 SchemaTypeConfigBuilder()
4560 .SetType("A")
4561 .AddProperty(PropertyConfigBuilder()
4562 .SetName("blob")
4563 .SetDataType(TYPE_BLOB_HANDLE)
4564 .SetCardinality(CARDINALITY_OPTIONAL))
4565 .AddProperty(
4566 PropertyConfigBuilder()
4567 .SetName("nonBlob")
4568 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
4569 .SetCardinality(CARDINALITY_OPTIONAL))
4570 .Build();
4571
4572 SchemaTypeConfigProto type_b =
4573 SchemaTypeConfigBuilder()
4574 .SetType("B")
4575 .AddProperty(
4576 PropertyConfigBuilder()
4577 .SetName("typeA")
4578 .SetDataTypeDocument("A", /*index_nested_properties=*/false)
4579 .SetCardinality(CARDINALITY_OPTIONAL))
4580 .AddProperty(
4581 PropertyConfigBuilder()
4582 .SetName("nonBlob")
4583 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
4584 .SetCardinality(CARDINALITY_OPTIONAL))
4585 .Build();
4586
4587 SchemaTypeConfigProto type_c =
4588 SchemaTypeConfigBuilder()
4589 .SetType("C")
4590 .AddProperty(
4591 PropertyConfigBuilder()
4592 .SetName("nonBlob")
4593 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
4594 .SetCardinality(CARDINALITY_OPTIONAL))
4595 .Build();
4596
4597 // type_a contains blob property.
4598 // type_b contains nested type_a, which contains blob property.
4599 // type_c contains no blob property.
4600 SchemaProto schema =
4601 SchemaBuilder().AddType(type_a).AddType(type_b).AddType(type_c).Build();
4602
4603 {
4604 // Create an instance of the schema store and set the schema.
4605 ICING_ASSERT_OK_AND_ASSIGN(
4606 std::unique_ptr<SchemaStore> schema_store,
4607 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4608 feature_flags_.get()));
4609 ICING_ASSERT_OK(schema_store->SetSchema(
4610 schema, /*ignore_errors_and_delete_documents=*/false));
4611
4612 EXPECT_THAT(schema_store->ConstructBlobPropertyMap(),
4613 IsOkAndHolds(UnorderedElementsAre(
4614 Pair("A", UnorderedElementsAre("blob")),
4615 Pair("B", UnorderedElementsAre("typeA.blob")))));
4616 }
4617 }
4618
TEST_F(SchemaStoreTest,GetTypeWithMultiLevelBlobProperties)4619 TEST_F(SchemaStoreTest, GetTypeWithMultiLevelBlobProperties) {
4620 SchemaTypeConfigProto type_a =
4621 SchemaTypeConfigBuilder()
4622 .SetType("A")
4623 .AddProperty(PropertyConfigBuilder()
4624 .SetName("blob")
4625 .SetDataType(TYPE_BLOB_HANDLE)
4626 .SetCardinality(CARDINALITY_OPTIONAL))
4627 .AddProperty(
4628 PropertyConfigBuilder()
4629 .SetName("nonBlob")
4630 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
4631 .SetCardinality(CARDINALITY_OPTIONAL))
4632 .Build();
4633
4634 SchemaTypeConfigProto type_b =
4635 SchemaTypeConfigBuilder()
4636 .SetType("B")
4637 .AddProperty(
4638 PropertyConfigBuilder()
4639 .SetName("typeA")
4640 .SetDataTypeDocument("A", /*index_nested_properties=*/false)
4641 .SetCardinality(CARDINALITY_OPTIONAL))
4642 .AddProperty(
4643 PropertyConfigBuilder()
4644 .SetName("nonBlob")
4645 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
4646 .SetCardinality(CARDINALITY_OPTIONAL))
4647 .Build();
4648
4649 SchemaTypeConfigProto type_c =
4650 SchemaTypeConfigBuilder()
4651 .SetType("C")
4652 .AddProperty(
4653 PropertyConfigBuilder()
4654 .SetName("typeB")
4655 .SetDataTypeDocument("B", /*index_nested_properties=*/false)
4656 .SetCardinality(CARDINALITY_OPTIONAL))
4657 .AddProperty(PropertyConfigBuilder()
4658 .SetName("blob")
4659 .SetDataType(TYPE_BLOB_HANDLE)
4660 .SetCardinality(CARDINALITY_OPTIONAL))
4661 .AddProperty(
4662 PropertyConfigBuilder()
4663 .SetName("nonBlob")
4664 .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
4665 .SetCardinality(CARDINALITY_OPTIONAL))
4666 .Build();
4667
4668 // type_a contains blob property.
4669 // type_b contains nested type_a, which contains blob property.
4670 // type_c contains blob property and nested type_b, which contains blob
4671 // property.
4672 SchemaProto schema =
4673 SchemaBuilder().AddType(type_a).AddType(type_b).AddType(type_c).Build();
4674 {
4675 // Create an instance of the schema store and set the schema.
4676 ICING_ASSERT_OK_AND_ASSIGN(
4677 std::unique_ptr<SchemaStore> schema_store,
4678 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4679 feature_flags_.get()));
4680 ICING_ASSERT_OK(schema_store->SetSchema(
4681 schema, /*ignore_errors_and_delete_documents=*/false));
4682
4683 EXPECT_THAT(
4684 schema_store->ConstructBlobPropertyMap(),
4685 IsOkAndHolds(UnorderedElementsAre(
4686 Pair("A", UnorderedElementsAre("blob")),
4687 Pair("B", UnorderedElementsAre("typeA.blob")),
4688 Pair("C", UnorderedElementsAre("blob", "typeB.typeA.blob")))));
4689 }
4690 }
4691
TEST_F(SchemaStoreTest,GetScorablePropertyIndex_SchemaNotSet)4692 TEST_F(SchemaStoreTest, GetScorablePropertyIndex_SchemaNotSet) {
4693 ICING_ASSERT_OK_AND_ASSIGN(
4694 std::unique_ptr<SchemaStore> schema_store,
4695 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4696 feature_flags_.get()));
4697
4698 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4699 /*schema_type_id=*/0,
4700 /*property_path=*/"timestamp"),
4701 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
4702 }
4703
TEST_F(SchemaStoreTest,GetScorablePropertyIndex_InvalidSchemaTypeId)4704 TEST_F(SchemaStoreTest, GetScorablePropertyIndex_InvalidSchemaTypeId) {
4705 ICING_ASSERT_OK_AND_ASSIGN(
4706 std::unique_ptr<SchemaStore> schema_store,
4707 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4708 feature_flags_.get()));
4709
4710 // Set schema
4711 ICING_ASSERT_OK(schema_store->SetSchema(
4712 schema_, /*ignore_errors_and_delete_documents=*/false));
4713
4714 // non-existing schema type id
4715 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4716 /*schema_type_id=*/100,
4717 /*property_path=*/"timestamp"),
4718 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
4719 }
4720
TEST_F(SchemaStoreTest,GetScorablePropertyIndex_InvalidPropertyName)4721 TEST_F(SchemaStoreTest, GetScorablePropertyIndex_InvalidPropertyName) {
4722 ICING_ASSERT_OK_AND_ASSIGN(
4723 std::unique_ptr<SchemaStore> schema_store,
4724 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4725 feature_flags_.get()));
4726
4727 SchemaProto schema =
4728 SchemaBuilder()
4729 .AddType(
4730 SchemaTypeConfigBuilder()
4731 .SetType("email")
4732 .AddProperty(
4733 PropertyConfigBuilder()
4734 .SetName("subject")
4735 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
4736 .SetCardinality(CARDINALITY_OPTIONAL))
4737 .AddProperty(
4738 PropertyConfigBuilder()
4739 .SetName("scoreDouble")
4740 .SetDataType(PropertyConfigProto::DataType::DOUBLE)
4741 .SetCardinality(CARDINALITY_OPTIONAL)
4742 .SetScorableType(SCORABLE_TYPE_ENABLED))
4743 .AddProperty(PropertyConfigBuilder()
4744 .SetName("timestamp")
4745 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
4746 .SetCardinality(CARDINALITY_OPTIONAL)
4747 .SetScorableType(SCORABLE_TYPE_ENABLED)))
4748 .Build();
4749
4750 // Set schema
4751 ICING_ASSERT_OK(schema_store->SetSchema(
4752 schema, /*ignore_errors_and_delete_documents=*/false));
4753
4754 // non-scorable property
4755 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4756 /*schema_type_id=*/0,
4757 /*property_path=*/"subject"),
4758 IsOkAndHolds(Eq(std::nullopt)));
4759 // non-existing property
4760 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4761 /*schema_type_id=*/0,
4762 /*property_path=*/"non_existing"),
4763 IsOkAndHolds(Eq(std::nullopt)));
4764 }
4765
TEST_F(SchemaStoreTest,GetScorablePropertyIndex_Ok)4766 TEST_F(SchemaStoreTest, GetScorablePropertyIndex_Ok) {
4767 ICING_ASSERT_OK_AND_ASSIGN(
4768 std::unique_ptr<SchemaStore> schema_store,
4769 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4770 feature_flags_.get()));
4771
4772 // Set schema
4773 ICING_ASSERT_OK(schema_store->SetSchema(
4774 schema_, /*ignore_errors_and_delete_documents=*/false));
4775
4776 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4777 /*schema_type_id=*/0,
4778 /*property_path=*/"timestamp"),
4779 IsOkAndHolds(0));
4780 }
4781
TEST_F(SchemaStoreTest,GetOrderedScorablePropertyPaths_SchemaNotSet)4782 TEST_F(SchemaStoreTest, GetOrderedScorablePropertyPaths_SchemaNotSet) {
4783 ICING_ASSERT_OK_AND_ASSIGN(
4784 std::unique_ptr<SchemaStore> schema_store,
4785 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4786 feature_flags_.get()));
4787
4788 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
4789 /*schema_type_id=*/0),
4790 StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
4791 }
4792
TEST_F(SchemaStoreTest,GetOrderedScorablePropertyPaths_InvalidSchemaTypeId)4793 TEST_F(SchemaStoreTest, GetOrderedScorablePropertyPaths_InvalidSchemaTypeId) {
4794 ICING_ASSERT_OK_AND_ASSIGN(
4795 std::unique_ptr<SchemaStore> schema_store,
4796 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4797 feature_flags_.get()));
4798
4799 // Set schema
4800 ICING_ASSERT_OK(schema_store->SetSchema(
4801 schema_, /*ignore_errors_and_delete_documents=*/false));
4802
4803 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
4804 /*schema_type_id=*/100),
4805 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
4806 }
4807
TEST_F(SchemaStoreTest,GetOrderedScorablePropertyPaths_Ok)4808 TEST_F(SchemaStoreTest, GetOrderedScorablePropertyPaths_Ok) {
4809 ICING_ASSERT_OK_AND_ASSIGN(
4810 std::unique_ptr<SchemaStore> schema_store,
4811 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4812 feature_flags_.get()));
4813
4814 SchemaProto schema =
4815 SchemaBuilder()
4816 .AddType(
4817 SchemaTypeConfigBuilder()
4818 .SetType("email")
4819 .AddProperty(
4820 PropertyConfigBuilder()
4821 .SetName("subject")
4822 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
4823 .SetCardinality(CARDINALITY_OPTIONAL))
4824 .AddProperty(
4825 PropertyConfigBuilder()
4826 .SetName("scoreDouble")
4827 .SetDataType(PropertyConfigProto::DataType::DOUBLE)
4828 .SetCardinality(CARDINALITY_OPTIONAL)
4829 .SetScorableType(SCORABLE_TYPE_ENABLED))
4830 .AddProperty(PropertyConfigBuilder()
4831 .SetName("timestamp")
4832 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
4833 .SetCardinality(CARDINALITY_OPTIONAL)
4834 .SetScorableType(SCORABLE_TYPE_ENABLED)))
4835 .AddType(SchemaTypeConfigBuilder().SetType("message").AddProperty(
4836 PropertyConfigBuilder()
4837 .SetName("subject")
4838 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
4839 .SetCardinality(CARDINALITY_OPTIONAL)))
4840 .Build();
4841
4842 // Set schema
4843 ICING_ASSERT_OK(schema_store->SetSchema(
4844 schema, /*ignore_errors_and_delete_documents=*/false));
4845 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
4846 EXPECT_THAT(schema_store->GetSchemaTypeId("message"), IsOkAndHolds(1));
4847
4848 // no scorable properties under the schema, 'message'.
4849 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
4850 /*schema_type_id=*/1),
4851 IsOkAndHolds(Pointee(ElementsAre())));
4852
4853 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
4854 /*schema_type_id=*/0),
4855 IsOkAndHolds(Pointee(ElementsAre(
4856 EqualsScorablePropertyInfo("scoreDouble", TYPE_DOUBLE),
4857 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
4858 }
4859
TEST_F(SchemaStoreTest,ScorablePropertyManagerUpdatesUponSchemaChange)4860 TEST_F(SchemaStoreTest, ScorablePropertyManagerUpdatesUponSchemaChange) {
4861 ICING_ASSERT_OK_AND_ASSIGN(
4862 std::unique_ptr<SchemaStore> schema_store,
4863 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4864 feature_flags_.get()));
4865
4866 // Sets the initial schema
4867 ICING_ASSERT_OK(schema_store->SetSchema(
4868 schema_, /*ignore_errors_and_delete_documents=*/false));
4869
4870 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4871 /*schema_type_id=*/0,
4872 /*property_path=*/"timestamp"),
4873 IsOkAndHolds(0));
4874 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
4875 /*schema_type_id=*/0),
4876 IsOkAndHolds(Pointee(ElementsAre(
4877 EqualsScorablePropertyInfo("timestamp", TYPE_INT64)))));
4878
4879 // The new schema drops the type 'email', and adds a new type 'message'.
4880 SchemaProto new_schema =
4881 SchemaBuilder()
4882 .AddType(
4883 SchemaTypeConfigBuilder()
4884 .SetType("message")
4885 .AddProperty(
4886 // Add an indexed property so we generate
4887 // section metadata on it
4888 PropertyConfigBuilder()
4889 .SetName("content")
4890 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
4891 .SetCardinality(CARDINALITY_OPTIONAL))
4892 .AddProperty(PropertyConfigBuilder()
4893 .SetName("scoreInt")
4894 .SetDataTypeInt64(NUMERIC_MATCH_RANGE)
4895 .SetCardinality(CARDINALITY_OPTIONAL)
4896 .SetScorableType(SCORABLE_TYPE_ENABLED))
4897 .AddProperty(
4898 PropertyConfigBuilder()
4899 .SetName("scoreDouble")
4900 .SetDataType(PropertyConfigProto::DataType::DOUBLE)
4901 .SetCardinality(CARDINALITY_OPTIONAL)
4902 .SetScorableType(SCORABLE_TYPE_ENABLED)))
4903 .Build();
4904
4905 // Force updates the schema.
4906 ICING_ASSERT_OK(schema_store->SetSchema(
4907 new_schema, /*ignore_errors_and_delete_documents=*/true));
4908
4909 // "timestamp" is no longer a valid property name.
4910 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4911 /*schema_type_id=*/0,
4912 /*property_path=*/"timestamp"),
4913 IsOkAndHolds(Eq(std::nullopt)));
4914
4915 // ok cases for the new schema.
4916 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4917 /*schema_type_id=*/0,
4918 /*property_path=*/"scoreInt"),
4919 IsOkAndHolds(1));
4920 EXPECT_THAT(schema_store->GetScorablePropertyIndex(
4921 /*schema_type_id=*/0,
4922 /*property_path=*/"scoreDouble"),
4923 IsOkAndHolds(0));
4924 EXPECT_THAT(schema_store->GetOrderedScorablePropertyInfo(
4925 /*schema_type_id=*/0),
4926 IsOkAndHolds(Pointee(ElementsAre(
4927 EqualsScorablePropertyInfo("scoreDouble", TYPE_DOUBLE),
4928 EqualsScorablePropertyInfo("scoreInt", TYPE_INT64)))));
4929 }
4930
4931 class SchemaStoreTestWithParam
4932 : public SchemaStoreTest,
4933 public testing::WithParamInterface<version_util::StateChange> {};
4934
TEST_P(SchemaStoreTestWithParam,MigrateSchemaWithDatabaseMigration)4935 TEST_P(SchemaStoreTestWithParam, MigrateSchemaWithDatabaseMigration) {
4936 SchemaProto schema_no_database =
4937 SchemaBuilder()
4938 .AddType(SchemaTypeConfigBuilder()
4939 .SetType("db1/email")
4940 .AddProperty(PropertyConfigBuilder()
4941 .SetName("db1Subject")
4942 .SetDataTypeString(TERM_MATCH_EXACT,
4943 TOKENIZER_RFC822)
4944 .SetCardinality(CARDINALITY_OPTIONAL)))
4945 .AddType(SchemaTypeConfigBuilder()
4946 .SetType("db2/email")
4947 .AddProperty(PropertyConfigBuilder()
4948 .SetName("db2Subject")
4949 .SetDataTypeString(TERM_MATCH_EXACT,
4950 TOKENIZER_PLAIN)
4951 .SetCardinality(CARDINALITY_OPTIONAL)))
4952 .Build();
4953
4954 {
4955 // Create an instance of the schema store and set the schema.
4956 ICING_ASSERT_OK_AND_ASSIGN(
4957 std::unique_ptr<SchemaStore> schema_store,
4958 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
4959 feature_flags_.get()));
4960 ICING_ASSERT_OK(schema_store->SetSchema(
4961 schema_no_database, /*ignore_errors_and_delete_documents=*/false));
4962
4963 EXPECT_THAT(schema_store->GetSchema(),
4964 IsOkAndHolds(Pointee(EqualsProto(schema_no_database))));
4965 }
4966
4967 SchemaTypeConfigProto db1_email_rfc =
4968 SchemaTypeConfigBuilder()
4969 .SetType("db1/email")
4970 .SetDatabase("db1")
4971 .AddProperty(
4972 PropertyConfigBuilder()
4973 .SetName("db1Subject")
4974 .SetCardinality(CARDINALITY_OPTIONAL)
4975 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_RFC822))
4976 .Build();
4977 SchemaTypeConfigProto db1_email_no_rfc =
4978 SchemaTypeConfigBuilder()
4979 .SetType("db1/email")
4980 .SetDatabase("db1")
4981 .AddProperty(PropertyConfigBuilder()
4982 .SetName("db1Subject")
4983 .SetCardinality(CARDINALITY_OPTIONAL)
4984 .SetDataType(TYPE_STRING))
4985 .Build();
4986 SchemaTypeConfigProto db2_email =
4987 SchemaTypeConfigBuilder()
4988 .SetType("db2/email")
4989 .SetDatabase("db2")
4990 .AddProperty(PropertyConfigBuilder()
4991 .SetName("db2Subject")
4992 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
4993 .SetCardinality(CARDINALITY_OPTIONAL))
4994 .Build();
4995 SchemaProto full_schema_with_database_rfc =
4996 SchemaBuilder().AddType(db1_email_rfc).AddType(db2_email).Build();
4997 SchemaProto full_schema_with_database_no_rfc =
4998 SchemaBuilder().AddType(db1_email_no_rfc).AddType(db2_email).Build();
4999 SchemaProto db1_schema_rfc = SchemaBuilder().AddType(db1_email_rfc).Build();
5000 SchemaProto db1_schema_no_rfc =
5001 SchemaBuilder().AddType(db1_email_no_rfc).Build();
5002 SchemaProto db2_schema = SchemaBuilder().AddType(db2_email).Build();
5003
5004 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
5005 &filesystem_, schema_store_dir_, /*version_state_change=*/GetParam(),
5006 version_util::kVersion, /*perform_schema_database_migration=*/true));
5007 {
5008 // Create a new instance of the schema store and check that the database
5009 // field is populated.
5010 ICING_ASSERT_OK_AND_ASSIGN(
5011 std::unique_ptr<SchemaStore> schema_store,
5012 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
5013 feature_flags_.get()));
5014
5015 if (GetParam() == version_util::StateChange::kVersionZeroRollForward ||
5016 GetParam() == version_util::StateChange::kUndetermined) {
5017 // For these cases, the overlay schema is discarded and we fall back to
5018 // the backup schema (no rfc version).
5019 EXPECT_THAT(
5020 schema_store->GetSchema(),
5021 IsOkAndHolds(Pointee(EqualsProto(full_schema_with_database_no_rfc))));
5022 EXPECT_THAT(schema_store->GetSchema("db1"),
5023 IsOkAndHolds(EqualsProto(db1_schema_no_rfc)));
5024 EXPECT_THAT(schema_store->GetSchema("db2"),
5025 IsOkAndHolds(EqualsProto(db2_schema)));
5026 } else {
5027 EXPECT_THAT(
5028 schema_store->GetSchema(),
5029 IsOkAndHolds(Pointee(EqualsProto(full_schema_with_database_rfc))));
5030 EXPECT_THAT(schema_store->GetSchema("db1"),
5031 IsOkAndHolds(EqualsProto(db1_schema_rfc)));
5032 EXPECT_THAT(schema_store->GetSchema("db2"),
5033 IsOkAndHolds(EqualsProto(db2_schema)));
5034
5035 DocumentProto db1_email_doc =
5036 DocumentBuilder()
5037 .SetKey("namespace", "uri1")
5038 .SetSchema("db1/email")
5039 .AddStringProperty("db1Subject", "db1_subject")
5040 .Build();
5041 DocumentProto db2_email_doc =
5042 DocumentBuilder()
5043 .SetKey("namespace", "uri3")
5044 .SetSchema("db2/email")
5045 .AddStringProperty("db2Subject", "db2_subject")
5046 .Build();
5047
5048 // Verify that our in-memory structures are ok
5049 ICING_ASSERT_OK_AND_ASSIGN(SectionGroup section_group,
5050 schema_store->ExtractSections(db1_email_doc));
5051 EXPECT_THAT(section_group.string_sections[0].content,
5052 ElementsAre("db1_subject"));
5053 ICING_ASSERT_OK_AND_ASSIGN(section_group,
5054 schema_store->ExtractSections(db2_email_doc));
5055 EXPECT_THAT(section_group.string_sections[0].content,
5056 ElementsAre("db2_subject"));
5057
5058 // Verify that our persisted data are ok
5059 EXPECT_THAT(schema_store->GetSchemaTypeId("db1/email"), IsOkAndHolds(0));
5060 EXPECT_THAT(schema_store->GetSchemaTypeId("db2/email"), IsOkAndHolds(1));
5061 }
5062 }
5063 }
5064
TEST_P(SchemaStoreTestWithParam,MigrateSchemaWithDatabaseMigration_noDbPrefix)5065 TEST_P(SchemaStoreTestWithParam,
5066 MigrateSchemaWithDatabaseMigration_noDbPrefix) {
5067 SchemaTypeConfigProto email_rfc =
5068 SchemaTypeConfigBuilder()
5069 .SetType("email")
5070 .AddProperty(
5071 PropertyConfigBuilder()
5072 .SetName("subject")
5073 .SetCardinality(CARDINALITY_OPTIONAL)
5074 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_RFC822))
5075 .Build();
5076 SchemaTypeConfigProto email_no_rfc =
5077 SchemaTypeConfigBuilder()
5078 .SetType("email")
5079 .AddProperty(PropertyConfigBuilder()
5080 .SetName("subject")
5081 .SetCardinality(CARDINALITY_OPTIONAL)
5082 .SetDataType(TYPE_STRING))
5083 .Build();
5084 SchemaTypeConfigProto message =
5085 SchemaTypeConfigBuilder()
5086 .SetType("message")
5087 .AddProperty(PropertyConfigBuilder()
5088 .SetName("content")
5089 .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
5090 .SetCardinality(CARDINALITY_OPTIONAL))
5091 .Build();
5092
5093 SchemaProto original_schema =
5094 SchemaBuilder().AddType(email_rfc).AddType(message).Build();
5095
5096 {
5097 // Create an instance of the schema store and set the schema.
5098 ICING_ASSERT_OK_AND_ASSIGN(
5099 std::unique_ptr<SchemaStore> schema_store,
5100 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
5101 feature_flags_.get()));
5102 ICING_ASSERT_OK(schema_store->SetSchema(
5103 original_schema, /*ignore_errors_and_delete_documents=*/false));
5104
5105 EXPECT_THAT(schema_store->GetSchema(),
5106 IsOkAndHolds(Pointee(EqualsProto(original_schema))));
5107 }
5108
5109 SchemaProto backup_schema =
5110 SchemaBuilder().AddType(email_no_rfc).AddType(message).Build();
5111
5112 ICING_EXPECT_OK(SchemaStore::MigrateSchema(
5113 &filesystem_, schema_store_dir_, /*version_state_change=*/GetParam(),
5114 version_util::kVersion, /*perform_schema_database_migration=*/true));
5115
5116 {
5117 // Create a new instance of the schema store and check that the database
5118 // field is populated.
5119 ICING_ASSERT_OK_AND_ASSIGN(
5120 std::unique_ptr<SchemaStore> schema_store,
5121 SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_,
5122 feature_flags_.get()));
5123
5124 if (GetParam() == version_util::StateChange::kVersionZeroRollForward ||
5125 GetParam() == version_util::StateChange::kUndetermined) {
5126 // For these cases, the overlay schema is discarded and we fall back to
5127 // the backup schema (no rfc version).
5128 EXPECT_THAT(schema_store->GetSchema(),
5129 IsOkAndHolds(Pointee(EqualsProto(backup_schema))));
5130 EXPECT_THAT(schema_store->GetSchema("db"),
5131 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND));
5132 } else {
5133 EXPECT_THAT(schema_store->GetSchema(),
5134 IsOkAndHolds(Pointee(EqualsProto(original_schema))));
5135 EXPECT_THAT(schema_store->GetSchema("db"),
5136 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND));
5137
5138 DocumentProto email_doc = DocumentBuilder()
5139 .SetKey("namespace", "uri1")
5140 .SetSchema("email")
5141 .AddStringProperty("subject", "subject")
5142 .Build();
5143 DocumentProto message_doc = DocumentBuilder()
5144 .SetKey("namespace", "uri2")
5145 .SetSchema("message")
5146 .AddStringProperty("content", "content")
5147 .Build();
5148
5149 // Verify that our in-memory structures are ok
5150 ICING_ASSERT_OK_AND_ASSIGN(SectionGroup section_group,
5151 schema_store->ExtractSections(email_doc));
5152 EXPECT_THAT(section_group.string_sections[0].content,
5153 ElementsAre("subject"));
5154 ICING_ASSERT_OK_AND_ASSIGN(section_group,
5155 schema_store->ExtractSections(message_doc));
5156 EXPECT_THAT(section_group.string_sections[0].content,
5157 ElementsAre("content"));
5158
5159 // Verify that our persisted data are ok
5160 EXPECT_THAT(schema_store->GetSchemaTypeId("email"), IsOkAndHolds(0));
5161 EXPECT_THAT(schema_store->GetSchemaTypeId("message"), IsOkAndHolds(1));
5162 }
5163 }
5164 }
5165
5166 INSTANTIATE_TEST_SUITE_P(
5167 SchemaStoreTestWithParam, SchemaStoreTestWithParam,
5168 testing::Values(
5169 /*version_state_change=*/
5170 version_util::StateChange::kUndetermined,
5171 version_util::StateChange::kCompatible,
5172 version_util::StateChange::kRollForward,
5173 version_util::StateChange::kRollBack,
5174 version_util::StateChange::kUpgrade,
5175 version_util::StateChange::kVersionZeroUpgrade,
5176 version_util::StateChange::kVersionZeroRollForward));
5177
5178 } // namespace
5179
5180 } // namespace lib
5181 } // namespace icing
5182