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