diff options
author | Grace Zhao <gracezrx@google.com> | 2023-06-28 15:35:44 -0700 |
---|---|---|
committer | Grace Zhao <gracezrx@google.com> | 2023-06-28 22:39:16 +0000 |
commit | b8e8a914804b5ef6ee8ca77a9ca7b9797fe55e94 (patch) | |
tree | fa0309938bf2b0d5155b94cb2516cb3142536556 | |
parent | 3a5d5836780120152cfade6a77ba233efbac5070 (diff) | |
parent | f1f564329ad560f580c03f9e928057ad4e4fadc9 (diff) | |
download | icing-b8e8a914804b5ef6ee8ca77a9ca7b9797fe55e94.tar.gz |
Merge remote-tracking branch 'aosp/upstream-master' into androidx-main
* aosp/upstream-master:
Update Icing from upstream.
Descriptions:
========================================================================
[Join][cleanup][ez] Rename QualifiedIdTypeJoinableIndex as QualifiedIdJoinIndex
========================================================================
Better evaluation for persistent hash map size in join index
========================================================================
Add num_to_score into ResultSpecProto
========================================================================
[Indexable_nested_properties_list 1/5] Add indexable_nested_properties to DocumentIndexingConfig and check it during schema validation.
========================================================================
[Indexable_nested_properties_list 2/5] Add check for indexable_neseted_properties when computing SchemaDelta
========================================================================
[Indexable_nested_properties_list 3/5] Add indexable_nested_properties to schema-property-iterator
========================================================================
[Indexable_nested_properties_list 4/5] Integrate circular schema definitions with schema-property-iterator
========================================================================
Bug: 272145329
Bug: 284232550
Bug: 285636503
Bug: 265304217
Bug: 289150947
Change-Id: Icfcd4901015b75f4af2d231f8a8613c6e33034a0
31 files changed, 3649 insertions, 487 deletions
diff --git a/icing/icing-search-engine.cc b/icing/icing-search-engine.cc index 2cdf930..3ffb297 100644 --- a/icing/icing-search-engine.cc +++ b/icing/icing-search-engine.cc @@ -42,8 +42,8 @@ #include "icing/index/numeric/integer-index.h" #include "icing/index/string-section-indexing-handler.h" #include "icing/join/join-processor.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/join/qualified-id-join-indexing-handler.h" -#include "icing/join/qualified-id-type-joinable-index.h" #include "icing/legacy/index/icing-filesystem.h" #include "icing/portable/endian.h" #include "icing/proto/debug.pb.h" @@ -141,6 +141,15 @@ libtextclassifier3::Status ValidateResultSpec( "ResultSpecProto.num_total_bytes_per_page_threshold cannot be " "non-positive."); } + if (result_spec.max_joined_children_per_parent_to_return() < 0) { + return absl_ports::InvalidArgumentError( + "ResultSpecProto.max_joined_children_per_parent_to_return cannot be " + "negative."); + } + if (result_spec.num_to_score() <= 0) { + return absl_ports::InvalidArgumentError( + "ResultSpecProto.num_to_score cannot be non-positive."); + } // Validate ResultGroupings. std::unordered_set<int32_t> unique_entry_ids; ResultSpecProto::ResultGroupingType result_grouping_type = @@ -621,8 +630,8 @@ libtextclassifier3::Status IcingSearchEngine::InitializeMembers( if (!filesystem_->DeleteDirectoryRecursively(doc_store_dir.c_str()) || !filesystem_->DeleteDirectoryRecursively(index_dir.c_str()) || !IntegerIndex::Discard(*filesystem_, integer_index_dir).ok() || - !QualifiedIdTypeJoinableIndex::Discard(*filesystem_, - qualified_id_join_index_dir) + !QualifiedIdJoinIndex::Discard(*filesystem_, + qualified_id_join_index_dir) .ok()) { return absl_ports::InternalError(absl_ports::StrCat( "Could not delete directories: ", index_dir, ", ", integer_index_dir, @@ -666,11 +675,11 @@ libtextclassifier3::Status IcingSearchEngine::InitializeMembers( // Discard qualified id join index directory and instantiate a new one. std::string qualified_id_join_index_dir = MakeQualifiedIdJoinIndexWorkingPath(options_.base_dir()); - ICING_RETURN_IF_ERROR(QualifiedIdTypeJoinableIndex::Discard( + ICING_RETURN_IF_ERROR(QualifiedIdJoinIndex::Discard( *filesystem_, qualified_id_join_index_dir)); ICING_ASSIGN_OR_RETURN( qualified_id_join_index_, - QualifiedIdTypeJoinableIndex::Create( + QualifiedIdJoinIndex::Create( *filesystem_, std::move(qualified_id_join_index_dir), options_.pre_mapping_fbv(), options_.use_persistent_hash_map())); @@ -842,11 +851,11 @@ libtextclassifier3::Status IcingSearchEngine::InitializeIndex( std::string qualified_id_join_index_dir = MakeQualifiedIdJoinIndexWorkingPath(options_.base_dir()); InitializeStatsProto::RecoveryCause qualified_id_join_index_recovery_cause; - auto qualified_id_join_index_or = QualifiedIdTypeJoinableIndex::Create( + auto qualified_id_join_index_or = QualifiedIdJoinIndex::Create( *filesystem_, qualified_id_join_index_dir, options_.pre_mapping_fbv(), options_.use_persistent_hash_map()); if (!qualified_id_join_index_or.ok()) { - ICING_RETURN_IF_ERROR(QualifiedIdTypeJoinableIndex::Discard( + ICING_RETURN_IF_ERROR(QualifiedIdJoinIndex::Discard( *filesystem_, qualified_id_join_index_dir)); qualified_id_join_index_recovery_cause = InitializeStatsProto::IO_ERROR; @@ -854,7 +863,7 @@ libtextclassifier3::Status IcingSearchEngine::InitializeIndex( // Try recreating it from scratch and rebuild everything. ICING_ASSIGN_OR_RETURN( qualified_id_join_index_, - QualifiedIdTypeJoinableIndex::Create( + QualifiedIdJoinIndex::Create( *filesystem_, std::move(qualified_id_join_index_dir), options_.pre_mapping_fbv(), options_.use_persistent_hash_map())); } else { @@ -2124,7 +2133,7 @@ IcingSearchEngine::QueryScoringResults IcingSearchEngine::ProcessQueryAndScore( std::move(scoring_processor_or).ValueOrDie(); std::vector<ScoredDocumentHit> scored_document_hits = scoring_processor->Score(std::move(query_results.root_iterator), - performance_configuration_.num_to_score, + result_spec.num_to_score(), &query_results.query_term_iterators); int64_t scoring_latency_ms = component_timer->GetElapsedMilliseconds(); @@ -2471,13 +2480,12 @@ IcingSearchEngine::CreateDataIndexingHandlers() { clock_.get(), integer_index_.get())); handlers.push_back(std::move(integer_section_indexing_handler)); - // Qualified id joinable property index handler + // Qualified id join index handler ICING_ASSIGN_OR_RETURN(std::unique_ptr<QualifiedIdJoinIndexingHandler> - qualified_id_joinable_property_indexing_handler, + qualified_id_join_indexing_handler, QualifiedIdJoinIndexingHandler::Create( clock_.get(), qualified_id_join_index_.get())); - handlers.push_back( - std::move(qualified_id_joinable_property_indexing_handler)); + handlers.push_back(std::move(qualified_id_join_indexing_handler)); return handlers; } diff --git a/icing/icing-search-engine.h b/icing/icing-search-engine.h index 15da142..d9d5ff6 100644 --- a/icing/icing-search-engine.h +++ b/icing/icing-search-engine.h @@ -31,7 +31,7 @@ #include "icing/index/numeric/numeric-index.h" #include "icing/jni/jni-cache.h" #include "icing/join/join-children-fetcher.h" -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/legacy/index/icing-filesystem.h" #include "icing/performance-configuration.h" #include "icing/proto/debug.pb.h" @@ -479,7 +479,7 @@ class IcingSearchEngine { ICING_GUARDED_BY(mutex_); // Storage for all join qualified ids from the document store. - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index_ + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index_ ICING_GUARDED_BY(mutex_); // Pointer to JNI class references diff --git a/icing/icing-search-engine_initialization_test.cc b/icing/icing-search-engine_initialization_test.cc index 74cc78f..64bcc98 100644 --- a/icing/icing-search-engine_initialization_test.cc +++ b/icing/icing-search-engine_initialization_test.cc @@ -34,8 +34,8 @@ #include "icing/jni/jni-cache.h" #include "icing/join/doc-join-info.h" #include "icing/join/join-processor.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/join/qualified-id-join-indexing-handler.h" -#include "icing/join/qualified-id-type-joinable-index.h" #include "icing/legacy/index/icing-filesystem.h" #include "icing/legacy/index/icing-mock-filesystem.h" #include "icing/portable/endian.h" @@ -3571,10 +3571,10 @@ TEST_F(IcingSearchEngineInitializationTest, { Filesystem filesystem; ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index, - QualifiedIdTypeJoinableIndex::Create( - filesystem, GetQualifiedIdJoinIndexDir(), /*pre_mapping_fbv=*/false, - /*use_persistent_hash_map=*/false)); + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index, + QualifiedIdJoinIndex::Create(filesystem, GetQualifiedIdJoinIndexDir(), + /*pre_mapping_fbv=*/false, + /*use_persistent_hash_map=*/false)); // Add data for document 0. ASSERT_THAT(qualified_id_join_index->last_added_document_id(), kInvalidDocumentId); @@ -3641,10 +3641,10 @@ TEST_F(IcingSearchEngineInitializationTest, { Filesystem filesystem; ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index, - QualifiedIdTypeJoinableIndex::Create( - filesystem, GetQualifiedIdJoinIndexDir(), /*pre_mapping_fbv=*/false, - /*use_persistent_hash_map=*/false)); + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index, + QualifiedIdJoinIndex::Create(filesystem, GetQualifiedIdJoinIndexDir(), + /*pre_mapping_fbv=*/false, + /*use_persistent_hash_map=*/false)); EXPECT_THAT(qualified_id_join_index->Get( DocJoinInfo(/*document_id=*/0, /*joinable_property_id=*/0)), StatusIs(libtextclassifier3::StatusCode::NOT_FOUND)); @@ -3742,10 +3742,10 @@ TEST_F(IcingSearchEngineInitializationTest, { Filesystem filesystem; ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index, - QualifiedIdTypeJoinableIndex::Create( - filesystem, GetQualifiedIdJoinIndexDir(), /*pre_mapping_fbv=*/false, - /*use_persistent_hash_map=*/false)); + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index, + QualifiedIdJoinIndex::Create(filesystem, GetQualifiedIdJoinIndexDir(), + /*pre_mapping_fbv=*/false, + /*use_persistent_hash_map=*/false)); // Add data for document 4. DocumentId original_last_added_doc_id = qualified_id_join_index->last_added_document_id(); @@ -3839,10 +3839,9 @@ TEST_F(IcingSearchEngineInitializationTest, // `name:person` with a child query for `body:consectetur` based on the // child's `senderQualifiedId` field. - // Add document 4 without "senderQualifiedId". If joinable index is not - // rebuilt correctly, then it will still have the previously added - // senderQualifiedId for document 4 and include document 4 incorrectly in - // the right side. + // Add document 4 without "senderQualifiedId". If join index is not rebuilt + // correctly, then it will still have the previously added senderQualifiedId + // for document 4 and include document 4 incorrectly in the right side. DocumentProto another_message = DocumentBuilder() .SetKey("namespace", "message/4") @@ -4829,7 +4828,7 @@ TEST_F(IcingSearchEngineInitializationTest, auto mock_filesystem = std::make_unique<MockFilesystem>(); EXPECT_CALL(*mock_filesystem, PRead(A<const char*>(), _, _, _)) .WillRepeatedly(DoDefault()); - // This fails QualifiedIdTypeJoinableIndex::Create() once. + // This fails QualifiedIdJoinIndex::Create() once. EXPECT_CALL( *mock_filesystem, PRead(Matcher<const char*>(Eq(qualified_id_join_index_metadata_file)), _, @@ -5146,8 +5145,8 @@ TEST_P(IcingSearchEngineInitializationVersionChangeTest, /*pre_mapping_fbv=*/false)); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index, - QualifiedIdTypeJoinableIndex::Create( + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index, + QualifiedIdJoinIndex::Create( *filesystem(), GetQualifiedIdJoinIndexDir(), /*pre_mapping_fbv=*/false, /*use_persistent_hash_map=*/false)); @@ -5160,16 +5159,14 @@ TEST_P(IcingSearchEngineInitializationVersionChangeTest, integer_section_indexing_handler, IntegerSectionIndexingHandler::Create( &fake_clock, integer_index.get())); - ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdJoinIndexingHandler> - qualified_id_joinable_property_indexing_handler, - QualifiedIdJoinIndexingHandler::Create(&fake_clock, - qualified_id_join_index.get())); + ICING_ASSERT_OK_AND_ASSIGN(std::unique_ptr<QualifiedIdJoinIndexingHandler> + qualified_id_join_indexing_handler, + QualifiedIdJoinIndexingHandler::Create( + &fake_clock, qualified_id_join_index.get())); std::vector<std::unique_ptr<DataIndexingHandler>> handlers; handlers.push_back(std::move(string_section_indexing_handler)); handlers.push_back(std::move(integer_section_indexing_handler)); - handlers.push_back( - std::move(qualified_id_joinable_property_indexing_handler)); + handlers.push_back(std::move(qualified_id_join_indexing_handler)); IndexProcessor index_processor(std::move(handlers), &fake_clock); DocumentProto incorrect_message = diff --git a/icing/icing-search-engine_schema_test.cc b/icing/icing-search-engine_schema_test.cc index 2609cce..1987afe 100644 --- a/icing/icing-search-engine_schema_test.cc +++ b/icing/icing-search-engine_schema_test.cc @@ -1614,8 +1614,8 @@ TEST_F(IcingSearchEngineSchemaTest, // - "senderQualifiedId": qualified id joinable. Joinable property id = 0. // // If the index is not correctly rebuilt, then the joinable data of - // "senderQualifiedId" in the joinable index will still have old joinable - // property id of 1 and therefore won't take effect for join search query. + // "senderQualifiedId" in the join index will still have old joinable property + // id of 1 and therefore won't take effect for join search query. SchemaProto email_without_receiver_schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( @@ -1917,8 +1917,8 @@ TEST_F( // - "zQualifiedId": qualified id joinable. Joinable property id = 1. // // If the index is not correctly rebuilt, then the joinable data of - // "senderQualifiedId" in the joinable index will still have old joinable - // property id of 1 and therefore won't take effect for join search query. + // "senderQualifiedId" in the join index will still have old joinable property + // id of 1 and therefore won't take effect for join search query. SchemaProto email_no_body_schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( diff --git a/icing/icing-search-engine_search_test.cc b/icing/icing-search-engine_search_test.cc index f1b49fb..fd2c939 100644 --- a/icing/icing-search-engine_search_test.cc +++ b/icing/icing-search-engine_search_test.cc @@ -502,6 +502,71 @@ TEST_P(IcingSearchEngineSearchTest, expected_search_result_proto)); } +TEST_P(IcingSearchEngineSearchTest, SearchWithNumToScore) { + auto fake_clock = std::make_unique<FakeClock>(); + fake_clock->SetTimerElapsedMilliseconds(1000); + TestIcingSearchEngine icing(GetDefaultIcingOptions(), + std::make_unique<Filesystem>(), + std::make_unique<IcingFilesystem>(), + std::move(fake_clock), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk()); + + DocumentProto document_one = CreateMessageDocument("namespace", "uri1"); + document_one.set_score(10); + ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk()); + + DocumentProto document_two = CreateMessageDocument("namespace", "uri2"); + document_two.set_score(5); + ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk()); + + SearchSpecProto search_spec; + search_spec.set_term_match_type(TermMatchType::PREFIX); + search_spec.set_query("message"); + search_spec.set_search_type(GetParam()); + + ResultSpecProto result_spec; + result_spec.set_num_per_page(10); + result_spec.set_num_to_score(10); + + ScoringSpecProto scoring_spec = GetDefaultScoringSpec(); + + SearchResultProto expected_search_result_proto1; + expected_search_result_proto1.mutable_status()->set_code(StatusProto::OK); + *expected_search_result_proto1.mutable_results()->Add()->mutable_document() = + document_one; + *expected_search_result_proto1.mutable_results()->Add()->mutable_document() = + document_two; + + SearchResultProto search_result_proto = + icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); + EXPECT_THAT(search_result_proto.status(), ProtoIsOk()); + EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( + expected_search_result_proto1)); + + result_spec.set_num_to_score(1); + // By setting num_to_score = 1, only document_two will be scored, ranked, and + // returned. + // - num_to_score cutoff is only affected by the reading order from posting + // list. IOW, since we read posting lists in doc id descending order, + // ScoringProcessor scores documents with higher doc ids first and cuts off + // if exceeding num_to_score. + // - Therefore, even though document_one has higher score, ScoringProcessor + // still skips document_one, because posting list reads document_two first + // and ScoringProcessor stops after document_two given that total # of + // scored document has already reached num_to_score. + SearchResultProto expected_search_result_google::protobuf; + expected_search_result_google::protobuf.mutable_status()->set_code(StatusProto::OK); + *expected_search_result_google::protobuf.mutable_results()->Add()->mutable_document() = + document_two; + + search_result_proto = + icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); + EXPECT_THAT(search_result_proto.status(), ProtoIsOk()); + EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( + expected_search_result_google::protobuf)); +} + TEST_P(IcingSearchEngineSearchTest, SearchNegativeResultLimitReturnsInvalidArgument) { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); @@ -577,6 +642,62 @@ TEST_P(IcingSearchEngineSearchTest, ProtoStatusIs(StatusProto::INVALID_ARGUMENT)); } +TEST_P(IcingSearchEngineSearchTest, + SearchNegativeMaxJoinedChildrenPerParentReturnsInvalidArgument) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + + SearchSpecProto search_spec; + search_spec.set_term_match_type(TermMatchType::PREFIX); + search_spec.set_query(""); + search_spec.set_search_type(GetParam()); + + ResultSpecProto result_spec; + result_spec.set_max_joined_children_per_parent_to_return(-1); + + SearchResultProto expected_search_result_proto; + expected_search_result_proto.mutable_status()->set_code( + StatusProto::INVALID_ARGUMENT); + expected_search_result_proto.mutable_status()->set_message( + "ResultSpecProto.max_joined_children_per_parent_to_return cannot be " + "negative."); + SearchResultProto actual_results = + icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); + EXPECT_THAT(actual_results, EqualsSearchResultIgnoreStatsAndScores( + expected_search_result_proto)); +} + +TEST_P(IcingSearchEngineSearchTest, + SearchNonPositiveNumToScoreReturnsInvalidArgument) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + + SearchSpecProto search_spec; + search_spec.set_term_match_type(TermMatchType::PREFIX); + search_spec.set_query(""); + search_spec.set_search_type(GetParam()); + + ResultSpecProto result_spec; + result_spec.set_num_to_score(-1); + + SearchResultProto expected_search_result_proto; + expected_search_result_proto.mutable_status()->set_code( + StatusProto::INVALID_ARGUMENT); + expected_search_result_proto.mutable_status()->set_message( + "ResultSpecProto.num_to_score cannot be non-positive."); + + SearchResultProto actual_results1 = + icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); + EXPECT_THAT(actual_results1, EqualsSearchResultIgnoreStatsAndScores( + expected_search_result_proto)); + + result_spec.set_num_to_score(0); + SearchResultProto actual_results2 = + icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); + EXPECT_THAT(actual_results2, EqualsSearchResultIgnoreStatsAndScores( + expected_search_result_proto)); +} + TEST_P(IcingSearchEngineSearchTest, SearchWithPersistenceReturnsValidResults) { IcingSearchEngineOptions icing_options = GetDefaultIcingOptions(); diff --git a/icing/index/index-processor_test.cc b/icing/index/index-processor_test.cc index 0a0108d..9505dbd 100644 --- a/icing/index/index-processor_test.cc +++ b/icing/index/index-processor_test.cc @@ -40,8 +40,8 @@ #include "icing/index/numeric/numeric-index.h" #include "icing/index/string-section-indexing-handler.h" #include "icing/index/term-property-id.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/join/qualified-id-join-indexing-handler.h" -#include "icing/join/qualified-id-type-joinable-index.h" #include "icing/legacy/index/icing-filesystem.h" #include "icing/legacy/index/icing-mock-filesystem.h" #include "icing/portable/platform.h" @@ -177,9 +177,9 @@ class IndexProcessorTest : public Test { ICING_ASSERT_OK_AND_ASSIGN( qualified_id_join_index_, - QualifiedIdTypeJoinableIndex::Create( - filesystem_, qualified_id_join_index_dir_, - /*pre_mapping_fbv=*/false, /*use_persistent_hash_map=*/false)); + QualifiedIdJoinIndex::Create(filesystem_, qualified_id_join_index_dir_, + /*pre_mapping_fbv=*/false, + /*use_persistent_hash_map=*/false)); language_segmenter_factory::SegmenterOptions segmenter_options(ULOC_US); ICING_ASSERT_OK_AND_ASSIGN( @@ -297,14 +297,13 @@ class IndexProcessorTest : public Test { &fake_clock_, integer_index_.get())); ICING_ASSERT_OK_AND_ASSIGN( std::unique_ptr<QualifiedIdJoinIndexingHandler> - qualified_id_joinable_property_indexing_handler, + qualified_id_join_indexing_handler, QualifiedIdJoinIndexingHandler::Create(&fake_clock_, qualified_id_join_index_.get())); std::vector<std::unique_ptr<DataIndexingHandler>> handlers; handlers.push_back(std::move(string_section_indexing_handler)); handlers.push_back(std::move(integer_section_indexing_handler)); - handlers.push_back( - std::move(qualified_id_joinable_property_indexing_handler)); + handlers.push_back(std::move(qualified_id_join_indexing_handler)); index_processor_ = std::make_unique<IndexProcessor>(std::move(handlers), &fake_clock_); @@ -339,7 +338,7 @@ class IndexProcessorTest : public Test { std::unique_ptr<Index> index_; std::unique_ptr<NumericIndex<int64_t>> integer_index_; - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index_; + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index_; std::unique_ptr<LanguageSegmenter> lang_segmenter_; std::unique_ptr<Normalizer> normalizer_; std::unique_ptr<SchemaStore> schema_store_; @@ -827,16 +826,14 @@ TEST_F(IndexProcessorTest, OutOfOrderDocumentIdsInRecoveryMode) { integer_section_indexing_handler, IntegerSectionIndexingHandler::Create( &fake_clock_, integer_index_.get())); - ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdJoinIndexingHandler> - qualified_id_joinable_property_indexing_handler, - QualifiedIdJoinIndexingHandler::Create(&fake_clock_, - qualified_id_join_index_.get())); + ICING_ASSERT_OK_AND_ASSIGN(std::unique_ptr<QualifiedIdJoinIndexingHandler> + qualified_id_join_indexing_handler, + QualifiedIdJoinIndexingHandler::Create( + &fake_clock_, qualified_id_join_index_.get())); std::vector<std::unique_ptr<DataIndexingHandler>> handlers; handlers.push_back(std::move(string_section_indexing_handler)); handlers.push_back(std::move(integer_section_indexing_handler)); - handlers.push_back( - std::move(qualified_id_joinable_property_indexing_handler)); + handlers.push_back(std::move(qualified_id_join_indexing_handler)); IndexProcessor index_processor(std::move(handlers), &fake_clock_, /*recovery_mode=*/true); diff --git a/icing/join/join-children-fetcher.h b/icing/join/join-children-fetcher.h index 5f799b8..1b875bc 100644 --- a/icing/join/join-children-fetcher.h +++ b/icing/join/join-children-fetcher.h @@ -44,7 +44,7 @@ class JoinChildrenFetcher { // Get a vector of children ScoredDocumentHit by parent document id. // // TODO(b/256022027): Implement property value joins with types of string and - // int. In these cases, GetChildren should look up joinable cache to fetch + // int. In these cases, GetChildren should look up join index to fetch // joinable property value of the given parent_doc_id according to // join_spec_.parent_property_expression, and then fetch children by the // corresponding map in this class using the joinable property value. diff --git a/icing/join/join-processor.h b/icing/join/join-processor.h index 347ce85..517e9db 100644 --- a/icing/join/join-processor.h +++ b/icing/join/join-processor.h @@ -22,7 +22,7 @@ #include "icing/text_classifier/lib3/utils/base/statusor.h" #include "icing/join/join-children-fetcher.h" -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/proto/search.pb.h" #include "icing/schema/schema-store.h" #include "icing/scoring/scored-document-hit.h" @@ -35,10 +35,10 @@ class JoinProcessor { public: static constexpr std::string_view kQualifiedIdExpr = "this.qualifiedId()"; - explicit JoinProcessor( - const DocumentStore* doc_store, const SchemaStore* schema_store, - const QualifiedIdTypeJoinableIndex* qualified_id_join_index, - int64_t current_time_ms) + explicit JoinProcessor(const DocumentStore* doc_store, + const SchemaStore* schema_store, + const QualifiedIdJoinIndex* qualified_id_join_index, + int64_t current_time_ms) : doc_store_(doc_store), schema_store_(schema_store), qualified_id_join_index_(qualified_id_join_index), @@ -72,14 +72,13 @@ class JoinProcessor { // - kInvalidDocumentId if the given document is not found, doesn't have // qualified id joinable type for the given property_path, or doesn't have // joinable value (an optional property) - // - Any other QualifiedIdTypeJoinableIndex errors + // - Any other QualifiedIdJoinIndex errors libtextclassifier3::StatusOr<DocumentId> FetchReferencedQualifiedId( const DocumentId& document_id, const std::string& property_path) const; const DocumentStore* doc_store_; // Does not own. const SchemaStore* schema_store_; // Does not own. - const QualifiedIdTypeJoinableIndex* - qualified_id_join_index_; // Does not own. + const QualifiedIdJoinIndex* qualified_id_join_index_; // Does not own. int64_t current_time_ms_; }; diff --git a/icing/join/join-processor_test.cc b/icing/join/join-processor_test.cc index 95d1392..3dd7c87 100644 --- a/icing/join/join-processor_test.cc +++ b/icing/join/join-processor_test.cc @@ -25,8 +25,8 @@ #include "icing/document-builder.h" #include "icing/file/filesystem.h" #include "icing/file/portable-file-backed-proto-log.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/join/qualified-id-join-indexing-handler.h" -#include "icing/join/qualified-id-type-joinable-index.h" #include "icing/portable/platform.h" #include "icing/proto/document.pb.h" #include "icing/proto/document_wrapper.pb.h" @@ -129,9 +129,9 @@ class JoinProcessorTest : public ::testing::Test { ICING_ASSERT_OK_AND_ASSIGN( qualified_id_join_index_, - QualifiedIdTypeJoinableIndex::Create( - filesystem_, qualified_id_join_index_dir_, - /*pre_mapping_fbv=*/false, /*use_persistent_hash_map=*/false)); + QualifiedIdJoinIndex::Create(filesystem_, qualified_id_join_index_dir_, + /*pre_mapping_fbv=*/false, + /*use_persistent_hash_map=*/false)); } void TearDown() override { @@ -185,7 +185,7 @@ class JoinProcessorTest : public ::testing::Test { std::unique_ptr<LanguageSegmenter> lang_segmenter_; std::unique_ptr<SchemaStore> schema_store_; std::unique_ptr<DocumentStore> doc_store_; - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index_; + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index_; FakeClock fake_clock_; }; diff --git a/icing/join/qualified-id-type-joinable-index.cc b/icing/join/qualified-id-join-index.cc index a1df3d0..86f1921 100644 --- a/icing/join/qualified-id-type-joinable-index.cc +++ b/icing/join/qualified-id-join-index.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include <cstring> #include <memory> @@ -43,6 +43,11 @@ namespace lib { namespace { +// Set 1M for max # of qualified id entries and 10 bytes for key-value bytes. +// This will take at most 23 MiB disk space and mmap for persistent hash map. +static constexpr int32_t kDocJoinInfoMapperMaxNumEntries = 1 << 20; +static constexpr int32_t kDocJoinInfoMapperAverageKVByteSize = 10; + static constexpr int32_t kDocJoinInfoMapperDynamicTrieMaxSize = 128 * 1024 * 1024; // 128 MiB @@ -69,12 +74,10 @@ std::string GetQualifiedIdStoragePath(std::string_view working_path) { } // namespace -/* static */ libtextclassifier3::StatusOr< - std::unique_ptr<QualifiedIdTypeJoinableIndex>> -QualifiedIdTypeJoinableIndex::Create(const Filesystem& filesystem, - std::string working_path, - bool pre_mapping_fbv, - bool use_persistent_hash_map) { +/* static */ libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>> +QualifiedIdJoinIndex::Create(const Filesystem& filesystem, + std::string working_path, bool pre_mapping_fbv, + bool use_persistent_hash_map) { if (!filesystem.FileExists(GetMetadataFilePath(working_path).c_str()) || !filesystem.DirectoryExists( GetDocJoinInfoMapperPath(working_path).c_str()) || @@ -90,7 +93,7 @@ QualifiedIdTypeJoinableIndex::Create(const Filesystem& filesystem, pre_mapping_fbv, use_persistent_hash_map); } -QualifiedIdTypeJoinableIndex::~QualifiedIdTypeJoinableIndex() { +QualifiedIdJoinIndex::~QualifiedIdJoinIndex() { if (!PersistToDisk().ok()) { ICING_LOG(WARNING) << "Failed to persist qualified id type joinable index " "to disk while destructing " @@ -98,7 +101,7 @@ QualifiedIdTypeJoinableIndex::~QualifiedIdTypeJoinableIndex() { } } -libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Put( +libtextclassifier3::Status QualifiedIdJoinIndex::Put( const DocJoinInfo& doc_join_info, std::string_view ref_qualified_id_str) { if (!doc_join_info.is_valid()) { return absl_ports::InvalidArgumentError( @@ -123,8 +126,8 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Put( return libtextclassifier3::Status::OK; } -libtextclassifier3::StatusOr<std::string_view> -QualifiedIdTypeJoinableIndex::Get(const DocJoinInfo& doc_join_info) const { +libtextclassifier3::StatusOr<std::string_view> QualifiedIdJoinIndex::Get( + const DocJoinInfo& doc_join_info) const { if (!doc_join_info.is_valid()) { return absl_ports::InvalidArgumentError( "Cannot get data for an invalid DocJoinInfo"); @@ -139,7 +142,7 @@ QualifiedIdTypeJoinableIndex::Get(const DocJoinInfo& doc_join_info) const { return std::string_view(data, strlen(data)); } -libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Optimize( +libtextclassifier3::Status QualifiedIdJoinIndex::Optimize( const std::vector<DocumentId>& document_id_old_to_new, DocumentId new_last_added_document_id) { std::string temp_working_path = working_path_ + "_temp"; @@ -157,10 +160,9 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Optimize( // Transfer all data from the current to new qualified id type joinable // index. Also PersistToDisk and destruct the instance after finishing, so // we can safely swap directories later. - ICING_ASSIGN_OR_RETURN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> new_index, - Create(filesystem_, temp_working_path_ddir.dir(), pre_mapping_fbv_, - use_persistent_hash_map_)); + ICING_ASSIGN_OR_RETURN(std::unique_ptr<QualifiedIdJoinIndex> new_index, + Create(filesystem_, temp_working_path_ddir.dir(), + pre_mapping_fbv_, use_persistent_hash_map_)); ICING_RETURN_IF_ERROR( TransferIndex(document_id_old_to_new, new_index.get())); new_index->set_last_added_document_id(new_last_added_document_id); @@ -190,7 +192,9 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Optimize( doc_join_info_mapper_, PersistentHashMapKeyMapper<int32_t>::Create( filesystem_, GetDocJoinInfoMapperPath(working_path_), - pre_mapping_fbv_)); + pre_mapping_fbv_, + /*max_num_entries=*/kDocJoinInfoMapperMaxNumEntries, + /*average_kv_byte_size=*/kDocJoinInfoMapperAverageKVByteSize)); } else { ICING_ASSIGN_OR_RETURN( doc_join_info_mapper_, @@ -210,7 +214,7 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Optimize( return libtextclassifier3::Status::OK; } -libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Clear() { +libtextclassifier3::Status QualifiedIdJoinIndex::Clear() { doc_join_info_mapper_.reset(); // Discard and reinitialize doc join info mapper. std::string doc_join_info_mapper_path = @@ -221,8 +225,9 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Clear() { ICING_ASSIGN_OR_RETURN( doc_join_info_mapper_, PersistentHashMapKeyMapper<int32_t>::Create( - filesystem_, std::move(doc_join_info_mapper_path), - pre_mapping_fbv_)); + filesystem_, std::move(doc_join_info_mapper_path), pre_mapping_fbv_, + /*max_num_entries=*/kDocJoinInfoMapperMaxNumEntries, + /*average_kv_byte_size=*/kDocJoinInfoMapperAverageKVByteSize)); } else { ICING_RETURN_IF_ERROR(DynamicTrieKeyMapper<int32_t>::Delete( filesystem_, doc_join_info_mapper_path)); @@ -243,12 +248,11 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::Clear() { return libtextclassifier3::Status::OK; } -/* static */ libtextclassifier3::StatusOr< - std::unique_ptr<QualifiedIdTypeJoinableIndex>> -QualifiedIdTypeJoinableIndex::InitializeNewFiles(const Filesystem& filesystem, - std::string&& working_path, - bool pre_mapping_fbv, - bool use_persistent_hash_map) { +/* static */ libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>> +QualifiedIdJoinIndex::InitializeNewFiles(const Filesystem& filesystem, + std::string&& working_path, + bool pre_mapping_fbv, + bool use_persistent_hash_map) { // Create working directory. if (!filesystem.CreateDirectoryRecursively(working_path.c_str())) { return absl_ports::InternalError( @@ -262,8 +266,9 @@ QualifiedIdTypeJoinableIndex::InitializeNewFiles(const Filesystem& filesystem, ICING_ASSIGN_OR_RETURN( doc_join_info_mapper, PersistentHashMapKeyMapper<int32_t>::Create( - filesystem, GetDocJoinInfoMapperPath(working_path), - pre_mapping_fbv)); + filesystem, GetDocJoinInfoMapperPath(working_path), pre_mapping_fbv, + /*max_num_entries=*/kDocJoinInfoMapperMaxNumEntries, + /*average_kv_byte_size=*/kDocJoinInfoMapperAverageKVByteSize)); } else { ICING_ASSIGN_OR_RETURN( doc_join_info_mapper, @@ -282,8 +287,8 @@ QualifiedIdTypeJoinableIndex::InitializeNewFiles(const Filesystem& filesystem, /*pre_mapping_mmap_size=*/pre_mapping_fbv ? 1024 * 1024 : 0)); // Create instance. - auto new_index = std::unique_ptr<QualifiedIdTypeJoinableIndex>( - new QualifiedIdTypeJoinableIndex( + auto new_index = + std::unique_ptr<QualifiedIdJoinIndex>(new QualifiedIdJoinIndex( filesystem, std::move(working_path), /*metadata_buffer=*/std::make_unique<uint8_t[]>(kMetadataFileSize), std::move(doc_join_info_mapper), std::move(qualified_id_storage), @@ -298,11 +303,11 @@ QualifiedIdTypeJoinableIndex::InitializeNewFiles(const Filesystem& filesystem, return new_index; } -/* static */ libtextclassifier3::StatusOr< - std::unique_ptr<QualifiedIdTypeJoinableIndex>> -QualifiedIdTypeJoinableIndex::InitializeExistingFiles( - const Filesystem& filesystem, std::string&& working_path, - bool pre_mapping_fbv, bool use_persistent_hash_map) { +/* static */ libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>> +QualifiedIdJoinIndex::InitializeExistingFiles(const Filesystem& filesystem, + std::string&& working_path, + bool pre_mapping_fbv, + bool use_persistent_hash_map) { // PRead metadata file. auto metadata_buffer = std::make_unique<uint8_t[]>(kMetadataFileSize); if (!filesystem.PRead(GetMetadataFilePath(working_path).c_str(), @@ -328,8 +333,9 @@ QualifiedIdTypeJoinableIndex::InitializeExistingFiles( ICING_ASSIGN_OR_RETURN( doc_join_info_mapper, PersistentHashMapKeyMapper<int32_t>::Create( - filesystem, GetDocJoinInfoMapperPath(working_path), - pre_mapping_fbv)); + filesystem, GetDocJoinInfoMapperPath(working_path), pre_mapping_fbv, + /*max_num_entries=*/kDocJoinInfoMapperMaxNumEntries, + /*average_kv_byte_size=*/kDocJoinInfoMapperAverageKVByteSize)); } else { ICING_ASSIGN_OR_RETURN( doc_join_info_mapper, @@ -348,8 +354,8 @@ QualifiedIdTypeJoinableIndex::InitializeExistingFiles( /*pre_mapping_mmap_size=*/pre_mapping_fbv ? 1024 * 1024 : 0)); // Create instance. - auto type_joinable_index = std::unique_ptr<QualifiedIdTypeJoinableIndex>( - new QualifiedIdTypeJoinableIndex( + auto type_joinable_index = + std::unique_ptr<QualifiedIdJoinIndex>(new QualifiedIdJoinIndex( filesystem, std::move(working_path), std::move(metadata_buffer), std::move(doc_join_info_mapper), std::move(qualified_id_storage), pre_mapping_fbv, use_persistent_hash_map)); @@ -364,9 +370,9 @@ QualifiedIdTypeJoinableIndex::InitializeExistingFiles( return type_joinable_index; } -libtextclassifier3::Status QualifiedIdTypeJoinableIndex::TransferIndex( +libtextclassifier3::Status QualifiedIdJoinIndex::TransferIndex( const std::vector<DocumentId>& document_id_old_to_new, - QualifiedIdTypeJoinableIndex* new_index) const { + QualifiedIdJoinIndex* new_index) const { std::unique_ptr<KeyMapper<int32_t>::Iterator> iter = doc_join_info_mapper_->GetIterator(); while (iter->Advance()) { @@ -394,8 +400,7 @@ libtextclassifier3::Status QualifiedIdTypeJoinableIndex::TransferIndex( return libtextclassifier3::Status::OK; } -libtextclassifier3::Status -QualifiedIdTypeJoinableIndex::PersistMetadataToDisk() { +libtextclassifier3::Status QualifiedIdJoinIndex::PersistMetadataToDisk() { std::string metadata_file_path = GetMetadataFilePath(working_path_); ScopedFd sfd(filesystem_.OpenForWrite(metadata_file_path.c_str())); @@ -415,20 +420,19 @@ QualifiedIdTypeJoinableIndex::PersistMetadataToDisk() { return libtextclassifier3::Status::OK; } -libtextclassifier3::Status -QualifiedIdTypeJoinableIndex::PersistStoragesToDisk() { +libtextclassifier3::Status QualifiedIdJoinIndex::PersistStoragesToDisk() { ICING_RETURN_IF_ERROR(doc_join_info_mapper_->PersistToDisk()); ICING_RETURN_IF_ERROR(qualified_id_storage_->PersistToDisk()); return libtextclassifier3::Status::OK; } libtextclassifier3::StatusOr<Crc32> -QualifiedIdTypeJoinableIndex::ComputeInfoChecksum() { +QualifiedIdJoinIndex::ComputeInfoChecksum() { return info().ComputeChecksum(); } libtextclassifier3::StatusOr<Crc32> -QualifiedIdTypeJoinableIndex::ComputeStoragesChecksum() { +QualifiedIdJoinIndex::ComputeStoragesChecksum() { ICING_ASSIGN_OR_RETURN(Crc32 doc_join_info_mapper_crc, doc_join_info_mapper_->ComputeChecksum()); ICING_ASSIGN_OR_RETURN(Crc32 qualified_id_storage_crc, diff --git a/icing/join/qualified-id-type-joinable-index.h b/icing/join/qualified-id-join-index.h index 4844433..6accfaf 100644 --- a/icing/join/qualified-id-type-joinable-index.h +++ b/icing/join/qualified-id-join-index.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ICING_JOIN_QUALIFIED_ID_TYPE_JOINABLE_INDEX_H_ -#define ICING_JOIN_QUALIFIED_ID_TYPE_JOINABLE_INDEX_H_ +#ifndef ICING_JOIN_QUALIFIED_ID_JOIN_INDEX_H_ +#define ICING_JOIN_QUALIFIED_ID_JOIN_INDEX_H_ #include <cstdint> #include <memory> @@ -34,9 +34,9 @@ namespace icing { namespace lib { -// QualifiedIdTypeJoinableIndex: a class to maintain data mapping DocJoinInfo to +// QualifiedIdJoinIndex: a class to maintain data mapping DocJoinInfo to // joinable qualified ids and delete propagation info. -class QualifiedIdTypeJoinableIndex : public PersistentStorage { +class QualifiedIdJoinIndex : public PersistentStorage { public: struct Info { static constexpr int32_t kMagic = 0x48cabdc6; @@ -61,23 +61,23 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { static constexpr WorkingPathType kWorkingPathType = WorkingPathType::kDirectory; - // Creates a QualifiedIdTypeJoinableIndex instance to store qualified ids for - // future joining search. If any of the underlying file is missing, then - // delete the whole working_path and (re)initialize with new ones. Otherwise - // initialize and create the instance by existing files. + // Creates a QualifiedIdJoinIndex instance to store qualified ids for future + // joining search. If any of the underlying file is missing, then delete the + // whole working_path and (re)initialize with new ones. Otherwise initialize + // and create the instance by existing files. // // filesystem: Object to make system level calls // working_path: Specifies the working path for PersistentStorage. - // QualifiedIdTypeJoinableIndex uses working path as working - // directory and all related files will be stored under this - // directory. It takes full ownership and of working_path_, - // including creation/deletion. It is the caller's - // responsibility to specify correct working path and avoid - // mixing different persistent storages together under the same - // path. Also the caller has the ownership for the parent - // directory of working_path_, and it is responsible for parent - // directory creation/deletion. See PersistentStorage for more - // details about the concept of working_path. + // QualifiedIdJoinIndex uses working path as working directory + // and all related files will be stored under this directory. It + // takes full ownership and of working_path_, including + // creation/deletion. It is the caller's responsibility to + // specify correct working path and avoid mixing different + // persistent storages together under the same path. Also the + // caller has the ownership for the parent directory of + // working_path_, and it is responsible for parent directory + // creation/deletion. See PersistentStorage for more details + // about the concept of working_path. // pre_mapping_fbv: flag indicating whether memory map max possible file size // for underlying FileBackedVector before growing the actual // file size. @@ -90,12 +90,11 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { // checksum // - INTERNAL_ERROR on I/O errors // - Any KeyMapper errors - static libtextclassifier3::StatusOr< - std::unique_ptr<QualifiedIdTypeJoinableIndex>> + static libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>> Create(const Filesystem& filesystem, std::string working_path, bool pre_mapping_fbv, bool use_persistent_hash_map); - // Deletes QualifiedIdTypeJoinableIndex under working_path. + // Deletes QualifiedIdJoinIndex under working_path. // // Returns: // - OK on success @@ -107,15 +106,13 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { } // Delete copy and move constructor/assignment operator. - QualifiedIdTypeJoinableIndex(const QualifiedIdTypeJoinableIndex&) = delete; - QualifiedIdTypeJoinableIndex& operator=(const QualifiedIdTypeJoinableIndex&) = - delete; + QualifiedIdJoinIndex(const QualifiedIdJoinIndex&) = delete; + QualifiedIdJoinIndex& operator=(const QualifiedIdJoinIndex&) = delete; - QualifiedIdTypeJoinableIndex(QualifiedIdTypeJoinableIndex&&) = delete; - QualifiedIdTypeJoinableIndex& operator=(QualifiedIdTypeJoinableIndex&&) = - delete; + QualifiedIdJoinIndex(QualifiedIdJoinIndex&&) = delete; + QualifiedIdJoinIndex& operator=(QualifiedIdJoinIndex&&) = delete; - ~QualifiedIdTypeJoinableIndex() override; + ~QualifiedIdJoinIndex() override; // Puts a new data into index: DocJoinInfo (DocumentId, JoinablePropertyId) // references to ref_qualified_id_str (the identifier of another document). @@ -183,7 +180,7 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { } private: - explicit QualifiedIdTypeJoinableIndex( + explicit QualifiedIdJoinIndex( const Filesystem& filesystem, std::string&& working_path, std::unique_ptr<uint8_t[]> metadata_buffer, std::unique_ptr<KeyMapper<int32_t>> doc_join_info_mapper, @@ -197,13 +194,11 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { pre_mapping_fbv_(pre_mapping_fbv), use_persistent_hash_map_(use_persistent_hash_map) {} - static libtextclassifier3::StatusOr< - std::unique_ptr<QualifiedIdTypeJoinableIndex>> + static libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>> InitializeNewFiles(const Filesystem& filesystem, std::string&& working_path, bool pre_mapping_fbv, bool use_persistent_hash_map); - static libtextclassifier3::StatusOr< - std::unique_ptr<QualifiedIdTypeJoinableIndex>> + static libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>> InitializeExistingFiles(const Filesystem& filesystem, std::string&& working_path, bool pre_mapping_fbv, bool use_persistent_hash_map); @@ -217,7 +212,7 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { // - INTERNAL_ERROR on I/O error libtextclassifier3::Status TransferIndex( const std::vector<DocumentId>& document_id_old_to_new, - QualifiedIdTypeJoinableIndex* new_index) const; + QualifiedIdJoinIndex* new_index) const; // Flushes contents of metadata file. // @@ -291,4 +286,4 @@ class QualifiedIdTypeJoinableIndex : public PersistentStorage { } // namespace lib } // namespace icing -#endif // ICING_JOIN_QUALIFIED_ID_TYPE_JOINABLE_INDEX_H_ +#endif // ICING_JOIN_QUALIFIED_ID_JOIN_INDEX_H_ diff --git a/icing/join/qualified-id-type-joinable-index_test.cc b/icing/join/qualified-id-join-index_test.cc index 8ef9167..3d59f4b 100644 --- a/icing/join/qualified-id-type-joinable-index_test.cc +++ b/icing/join/qualified-id-join-index_test.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include <memory> #include <string> @@ -49,7 +49,7 @@ using ::testing::Pointee; using ::testing::SizeIs; using Crcs = PersistentStorage::Crcs; -using Info = QualifiedIdTypeJoinableIndex::Info; +using Info = QualifiedIdJoinIndex::Info; static constexpr int32_t kCorruptedValueOffset = 3; @@ -63,7 +63,7 @@ struct QualifiedIdJoinIndexTestParam { use_persistent_hash_map(use_persistent_hash_map_in) {} }; -class QualifiedIdTypeJoinableIndexTest +class QualifiedIdJoinIndexTest : public ::testing::TestWithParam<QualifiedIdJoinIndexTestParam> { protected: void SetUp() override { @@ -71,7 +71,7 @@ class QualifiedIdTypeJoinableIndexTest ASSERT_THAT(filesystem_.CreateDirectoryRecursively(base_dir_.c_str()), IsTrue()); - working_path_ = base_dir_ + "/qualified_id_type_joinable_index_test"; + working_path_ = base_dir_ + "/qualified_id_join_index_test"; } void TearDown() override { @@ -83,27 +83,26 @@ class QualifiedIdTypeJoinableIndexTest std::string working_path_; }; -TEST_P(QualifiedIdTypeJoinableIndexTest, InvalidWorkingPath) { +TEST_P(QualifiedIdJoinIndexTest, InvalidWorkingPath) { const QualifiedIdJoinIndexTestParam& param = GetParam(); - EXPECT_THAT( - QualifiedIdTypeJoinableIndex::Create( - filesystem_, "/dev/null/qualified_id_type_joinable_index_test", - param.pre_mapping_fbv, param.use_persistent_hash_map), - StatusIs(libtextclassifier3::StatusCode::INTERNAL)); + EXPECT_THAT(QualifiedIdJoinIndex::Create( + filesystem_, "/dev/null/qualified_id_join_index_test", + param.pre_mapping_fbv, param.use_persistent_hash_map), + StatusIs(libtextclassifier3::StatusCode::INTERNAL)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, InitializeNewFiles) { +TEST_P(QualifiedIdJoinIndexTest, InitializeNewFiles) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ASSERT_FALSE(filesystem_.DirectoryExists(working_path_.c_str())); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index, Pointee(IsEmpty())); ICING_ASSERT_OK(index->PersistToDisk()); @@ -113,25 +112,23 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, InitializeNewFiles) { // sections. const std::string metadata_file_path = absl_ports::StrCat(working_path_, "/metadata"); - auto metadata_buffer = std::make_unique<uint8_t[]>( - QualifiedIdTypeJoinableIndex::kMetadataFileSize); + auto metadata_buffer = + std::make_unique<uint8_t[]>(QualifiedIdJoinIndex::kMetadataFileSize); ASSERT_THAT( filesystem_.PRead(metadata_file_path.c_str(), metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize, + QualifiedIdJoinIndex::kMetadataFileSize, /*offset=*/0), IsTrue()); // Check info section const Info* info = reinterpret_cast<const Info*>( - metadata_buffer.get() + - QualifiedIdTypeJoinableIndex::kInfoMetadataBufferOffset); + metadata_buffer.get() + QualifiedIdJoinIndex::kInfoMetadataBufferOffset); EXPECT_THAT(info->magic, Eq(Info::kMagic)); EXPECT_THAT(info->last_added_document_id, Eq(kInvalidDocumentId)); // Check crcs section const Crcs* crcs = reinterpret_cast<const Crcs*>( - metadata_buffer.get() + - QualifiedIdTypeJoinableIndex::kCrcsMetadataBufferOffset); + metadata_buffer.get() + QualifiedIdJoinIndex::kCrcsMetadataBufferOffset); // There are some initial info in KeyMapper, so storages_crc should be // non-zero. EXPECT_THAT(crcs->component_crcs.storages_crc, Ne(0)); @@ -146,16 +143,16 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, InitializeNewFiles) { .Get())); } -TEST_P(QualifiedIdTypeJoinableIndexTest, +TEST_P(QualifiedIdJoinIndexTest, InitializationShouldFailWithoutPersistToDiskOrDestruction) { const QualifiedIdJoinIndexTestParam& param = GetParam(); - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); // Insert some data. ICING_ASSERT_OK( @@ -171,24 +168,23 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, // Without calling PersistToDisk, checksums will not be recomputed or synced // to disk, so initializing another instance on the same files should fail. - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map), + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map), StatusIs(param.use_persistent_hash_map ? libtextclassifier3::StatusCode::FAILED_PRECONDITION : libtextclassifier3::StatusCode::INTERNAL)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, - InitializationShouldSucceedWithPersistToDisk) { +TEST_P(QualifiedIdJoinIndexTest, InitializationShouldSucceedWithPersistToDisk) { const QualifiedIdJoinIndexTestParam& param = GetParam(); - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index1, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index1, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); // Insert some data. ICING_ASSERT_OK( @@ -208,10 +204,10 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, ICING_EXPECT_OK(index1->PersistToDisk()); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index2, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index2, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index2, Pointee(SizeIs(3))); EXPECT_THAT( index2->Get(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20)), @@ -224,17 +220,16 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, IsOkAndHolds(/*ref_qualified_id_str=*/"namespace#uriC")); } -TEST_P(QualifiedIdTypeJoinableIndexTest, - InitializationShouldSucceedAfterDestruction) { +TEST_P(QualifiedIdJoinIndexTest, InitializationShouldSucceedAfterDestruction) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); // Insert some data. ICING_ASSERT_OK( @@ -255,10 +250,10 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, // thus initializing another instance on the same files should succeed, and // we should be able to get the same contents. ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index, Pointee(SizeIs(3))); EXPECT_THAT(index->Get(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20)), @@ -272,17 +267,17 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, } } -TEST_P(QualifiedIdTypeJoinableIndexTest, +TEST_P(QualifiedIdJoinIndexTest, InitializeExistingFilesWithDifferentMagicShouldFail) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20), /*ref_qualified_id_str=*/"namespace#uriA")); @@ -297,50 +292,49 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, ScopedFd metadata_sfd(filesystem_.OpenForWrite(metadata_file_path.c_str())); ASSERT_THAT(metadata_sfd.is_valid(), IsTrue()); - auto metadata_buffer = std::make_unique<uint8_t[]>( - QualifiedIdTypeJoinableIndex::kMetadataFileSize); - ASSERT_THAT( - filesystem_.PRead(metadata_sfd.get(), metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize, - /*offset=*/0), - IsTrue()); + auto metadata_buffer = + std::make_unique<uint8_t[]>(QualifiedIdJoinIndex::kMetadataFileSize); + ASSERT_THAT(filesystem_.PRead(metadata_sfd.get(), metadata_buffer.get(), + QualifiedIdJoinIndex::kMetadataFileSize, + /*offset=*/0), + IsTrue()); // Manually change magic and update checksums. Crcs* crcs = reinterpret_cast<Crcs*>( metadata_buffer.get() + - QualifiedIdTypeJoinableIndex::kCrcsMetadataBufferOffset); + QualifiedIdJoinIndex::kCrcsMetadataBufferOffset); Info* info = reinterpret_cast<Info*>( metadata_buffer.get() + - QualifiedIdTypeJoinableIndex::kInfoMetadataBufferOffset); + QualifiedIdJoinIndex::kInfoMetadataBufferOffset); info->magic += kCorruptedValueOffset; crcs->component_crcs.info_crc = info->ComputeChecksum().Get(); crcs->all_crc = crcs->component_crcs.ComputeChecksum().Get(); - ASSERT_THAT(filesystem_.PWrite( - metadata_sfd.get(), /*offset=*/0, metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize), + ASSERT_THAT(filesystem_.PWrite(metadata_sfd.get(), /*offset=*/0, + metadata_buffer.get(), + QualifiedIdJoinIndex::kMetadataFileSize), IsTrue()); } - // Attempt to create the qualified id type joinable index with different - // magic. This should fail. - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map), + // Attempt to create the qualified id join index with different magic. This + // should fail. + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map), StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION, HasSubstr("Incorrect magic value"))); } -TEST_P(QualifiedIdTypeJoinableIndexTest, +TEST_P(QualifiedIdJoinIndexTest, InitializeExistingFilesWithWrongAllCrcShouldFail) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20), /*ref_qualified_id_str=*/"namespace#uriA")); @@ -354,46 +348,45 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, ScopedFd metadata_sfd(filesystem_.OpenForWrite(metadata_file_path.c_str())); ASSERT_THAT(metadata_sfd.is_valid(), IsTrue()); - auto metadata_buffer = std::make_unique<uint8_t[]>( - QualifiedIdTypeJoinableIndex::kMetadataFileSize); - ASSERT_THAT( - filesystem_.PRead(metadata_sfd.get(), metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize, - /*offset=*/0), - IsTrue()); + auto metadata_buffer = + std::make_unique<uint8_t[]>(QualifiedIdJoinIndex::kMetadataFileSize); + ASSERT_THAT(filesystem_.PRead(metadata_sfd.get(), metadata_buffer.get(), + QualifiedIdJoinIndex::kMetadataFileSize, + /*offset=*/0), + IsTrue()); // Manually corrupt all_crc Crcs* crcs = reinterpret_cast<Crcs*>( metadata_buffer.get() + - QualifiedIdTypeJoinableIndex::kCrcsMetadataBufferOffset); + QualifiedIdJoinIndex::kCrcsMetadataBufferOffset); crcs->all_crc += kCorruptedValueOffset; - ASSERT_THAT(filesystem_.PWrite( - metadata_sfd.get(), /*offset=*/0, metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize), + ASSERT_THAT(filesystem_.PWrite(metadata_sfd.get(), /*offset=*/0, + metadata_buffer.get(), + QualifiedIdJoinIndex::kMetadataFileSize), IsTrue()); } - // Attempt to create the qualified id type joinable index with metadata - // containing corrupted all_crc. This should fail. - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map), + // Attempt to create the qualified id join index with metadata containing + // corrupted all_crc. This should fail. + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map), StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION, HasSubstr("Invalid all crc"))); } -TEST_P(QualifiedIdTypeJoinableIndexTest, +TEST_P(QualifiedIdJoinIndexTest, InitializeExistingFilesWithCorruptedInfoShouldFail) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20), /*ref_qualified_id_str=*/"namespace#uriA")); @@ -407,47 +400,46 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, ScopedFd metadata_sfd(filesystem_.OpenForWrite(metadata_file_path.c_str())); ASSERT_THAT(metadata_sfd.is_valid(), IsTrue()); - auto metadata_buffer = std::make_unique<uint8_t[]>( - QualifiedIdTypeJoinableIndex::kMetadataFileSize); - ASSERT_THAT( - filesystem_.PRead(metadata_sfd.get(), metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize, - /*offset=*/0), - IsTrue()); + auto metadata_buffer = + std::make_unique<uint8_t[]>(QualifiedIdJoinIndex::kMetadataFileSize); + ASSERT_THAT(filesystem_.PRead(metadata_sfd.get(), metadata_buffer.get(), + QualifiedIdJoinIndex::kMetadataFileSize, + /*offset=*/0), + IsTrue()); // Modify info, but don't update the checksum. This would be similar to // corruption of info. Info* info = reinterpret_cast<Info*>( metadata_buffer.get() + - QualifiedIdTypeJoinableIndex::kInfoMetadataBufferOffset); + QualifiedIdJoinIndex::kInfoMetadataBufferOffset); info->last_added_document_id += kCorruptedValueOffset; - ASSERT_THAT(filesystem_.PWrite( - metadata_sfd.get(), /*offset=*/0, metadata_buffer.get(), - QualifiedIdTypeJoinableIndex::kMetadataFileSize), + ASSERT_THAT(filesystem_.PWrite(metadata_sfd.get(), /*offset=*/0, + metadata_buffer.get(), + QualifiedIdJoinIndex::kMetadataFileSize), IsTrue()); } - // Attempt to create the qualified id type joinable index with info that - // doesn't match its checksum. This should fail. - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map), + // Attempt to create the qualified id join index with info that doesn't match + // its checksum. This should fail. + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map), StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION, HasSubstr("Invalid info crc"))); } -TEST_P(QualifiedIdTypeJoinableIndexTest, +TEST_P(QualifiedIdJoinIndexTest, InitializeExistingFilesWithCorruptedDocJoinInfoMapperShouldFail) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20), /*ref_qualified_id_str=*/"namespace#uriA")); @@ -478,26 +470,26 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, ASSERT_THAT(old_crc, Not(Eq(new_crc))); } - // Attempt to create the qualified id type joinable index with corrupted + // Attempt to create the qualified id join index with corrupted // doc_join_info_mapper. This should fail. - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map), + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map), StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION, HasSubstr("Invalid storages crc"))); } -TEST_P(QualifiedIdTypeJoinableIndexTest, +TEST_P(QualifiedIdJoinIndexTest, InitializeExistingFilesWithCorruptedQualifiedIdStorageShouldFail) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20), /*ref_qualified_id_str=*/"namespace#uriA")); @@ -524,24 +516,24 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, ASSERT_THAT(old_crc, Not(Eq(new_crc))); } - // Attempt to create the qualified id type joinable index with corrupted + // Attempt to create the qualified id join index with corrupted // qualified_id_storage. This should fail. - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map), + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map), StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION, HasSubstr("Invalid storages crc"))); } -TEST_P(QualifiedIdTypeJoinableIndexTest, InvalidPut) { +TEST_P(QualifiedIdJoinIndexTest, InvalidPut) { const QualifiedIdJoinIndexTestParam& param = GetParam(); - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); DocJoinInfo default_invalid; EXPECT_THAT( @@ -549,22 +541,22 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, InvalidPut) { StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, InvalidGet) { +TEST_P(QualifiedIdJoinIndexTest, InvalidGet) { const QualifiedIdJoinIndexTestParam& param = GetParam(); - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); DocJoinInfo default_invalid; EXPECT_THAT(index->Get(default_invalid), StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, PutAndGet) { +TEST_P(QualifiedIdJoinIndexTest, PutAndGet) { const QualifiedIdJoinIndexTestParam& param = GetParam(); DocJoinInfo target_info1(/*document_id=*/1, /*joinable_property_id=*/20); @@ -577,12 +569,12 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, PutAndGet) { std::string_view ref_qualified_id_str_c = "namespace#uriC"; { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index->Put(target_info1, ref_qualified_id_str_a), IsOk()); EXPECT_THAT(index->Put(target_info2, ref_qualified_id_str_b), IsOk()); @@ -598,29 +590,28 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, PutAndGet) { // Verify we can get all of them after destructing and re-initializing. ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index, Pointee(SizeIs(3))); EXPECT_THAT(index->Get(target_info1), IsOkAndHolds(ref_qualified_id_str_a)); EXPECT_THAT(index->Get(target_info2), IsOkAndHolds(ref_qualified_id_str_b)); EXPECT_THAT(index->Get(target_info3), IsOkAndHolds(ref_qualified_id_str_c)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, - GetShouldReturnNotFoundErrorIfNotExist) { +TEST_P(QualifiedIdJoinIndexTest, GetShouldReturnNotFoundErrorIfNotExist) { const QualifiedIdJoinIndexTestParam& param = GetParam(); DocJoinInfo target_info(/*document_id=*/1, /*joinable_property_id=*/20); std::string_view ref_qualified_id_str = "namespace#uriA"; - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); // Verify entry is not found in the beginning. EXPECT_THAT(index->Get(target_info), @@ -636,14 +627,14 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, StatusIs(libtextclassifier3::StatusCode::NOT_FOUND)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, SetLastAddedDocumentId) { +TEST_P(QualifiedIdJoinIndexTest, SetLastAddedDocumentId) { const QualifiedIdJoinIndexTestParam& param = GetParam(); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index->last_added_document_id(), Eq(kInvalidDocumentId)); @@ -657,15 +648,15 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, SetLastAddedDocumentId) { } TEST_P( - QualifiedIdTypeJoinableIndexTest, + QualifiedIdJoinIndexTest, SetLastAddedDocumentIdShouldIgnoreNewDocumentIdNotGreaterThanTheCurrent) { const QualifiedIdJoinIndexTestParam& param = GetParam(); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); constexpr DocumentId kDocumentId = 123; index->set_last_added_document_id(kDocumentId); @@ -678,14 +669,14 @@ TEST_P( EXPECT_THAT(index->last_added_document_id(), Eq(kDocumentId)); } -TEST_P(QualifiedIdTypeJoinableIndexTest, Optimize) { +TEST_P(QualifiedIdJoinIndexTest, Optimize) { const QualifiedIdJoinIndexTestParam& param = GetParam(); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/3, /*joinable_property_id=*/10), @@ -759,14 +750,14 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, Optimize) { IsOkAndHolds("namespace#uriD")); } -TEST_P(QualifiedIdTypeJoinableIndexTest, OptimizeOutOfRangeDocumentId) { +TEST_P(QualifiedIdJoinIndexTest, OptimizeOutOfRangeDocumentId) { const QualifiedIdJoinIndexTestParam& param = GetParam(); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/99, /*joinable_property_id=*/10), @@ -788,14 +779,14 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, OptimizeOutOfRangeDocumentId) { EXPECT_THAT(index, Pointee(IsEmpty())); } -TEST_P(QualifiedIdTypeJoinableIndexTest, OptimizeDeleteAll) { +TEST_P(QualifiedIdJoinIndexTest, OptimizeDeleteAll) { const QualifiedIdJoinIndexTestParam& param = GetParam(); ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/3, /*joinable_property_id=*/10), @@ -827,19 +818,19 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, OptimizeDeleteAll) { EXPECT_THAT(index, Pointee(IsEmpty())); } -TEST_P(QualifiedIdTypeJoinableIndexTest, Clear) { +TEST_P(QualifiedIdJoinIndexTest, Clear) { const QualifiedIdJoinIndexTestParam& param = GetParam(); DocJoinInfo target_info1(/*document_id=*/1, /*joinable_property_id=*/20); DocJoinInfo target_info2(/*document_id=*/3, /*joinable_property_id=*/5); DocJoinInfo target_info3(/*document_id=*/6, /*joinable_property_id=*/13); - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(target_info1, /*ref_qualified_id_str=*/"namespace#uriA")); ICING_ASSERT_OK( @@ -862,7 +853,7 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, Clear) { EXPECT_THAT(index->Get(target_info3), StatusIs(libtextclassifier3::StatusCode::NOT_FOUND)); - // Joinable index should be able to work normally after Clear(). + // Join index should be able to work normally after Clear(). DocJoinInfo target_info4(/*document_id=*/2, /*joinable_property_id=*/19); ICING_ASSERT_OK( index->Put(target_info4, /*ref_qualified_id_str=*/"namespace#uriD")); @@ -876,9 +867,9 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, Clear) { // Verify index after reconstructing. ICING_ASSERT_OK_AND_ASSIGN( - index, QualifiedIdTypeJoinableIndex::Create( - filesystem_, working_path_, param.pre_mapping_fbv, - param.use_persistent_hash_map)); + index, QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); EXPECT_THAT(index->last_added_document_id(), Eq(2)); EXPECT_THAT(index->Get(target_info1), StatusIs(libtextclassifier3::StatusCode::NOT_FOUND)); @@ -889,16 +880,16 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, Clear) { EXPECT_THAT(index->Get(target_info4), IsOkAndHolds("namespace#uriD")); } -TEST_P(QualifiedIdTypeJoinableIndexTest, SwitchKeyMapperTypeShouldReturnError) { +TEST_P(QualifiedIdJoinIndexTest, SwitchKeyMapperTypeShouldReturnError) { const QualifiedIdJoinIndexTestParam& param = GetParam(); { - // Create new qualified id type joinable index + // Create new qualified id join index ICING_ASSERT_OK_AND_ASSIGN( - std::unique_ptr<QualifiedIdTypeJoinableIndex> index, - QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - param.use_persistent_hash_map)); + std::unique_ptr<QualifiedIdJoinIndex> index, + QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + param.use_persistent_hash_map)); ICING_ASSERT_OK( index->Put(DocJoinInfo(/*document_id=*/1, /*joinable_property_id=*/20), /*ref_qualified_id_str=*/"namespace#uriA")); @@ -907,14 +898,14 @@ TEST_P(QualifiedIdTypeJoinableIndexTest, SwitchKeyMapperTypeShouldReturnError) { } bool switch_key_mapper_flag = !param.use_persistent_hash_map; - EXPECT_THAT(QualifiedIdTypeJoinableIndex::Create(filesystem_, working_path_, - param.pre_mapping_fbv, - switch_key_mapper_flag), + EXPECT_THAT(QualifiedIdJoinIndex::Create(filesystem_, working_path_, + param.pre_mapping_fbv, + switch_key_mapper_flag), StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION)); } INSTANTIATE_TEST_SUITE_P( - QualifiedIdTypeJoinableIndexTest, QualifiedIdTypeJoinableIndexTest, + QualifiedIdJoinIndexTest, QualifiedIdJoinIndexTest, testing::Values( QualifiedIdJoinIndexTestParam(/*pre_mapping_fbv_in=*/true, /*use_persistent_hash_map_in=*/true), diff --git a/icing/join/qualified-id-join-indexing-handler.cc b/icing/join/qualified-id-join-indexing-handler.cc index 86af043..344cf41 100644 --- a/icing/join/qualified-id-join-indexing-handler.cc +++ b/icing/join/qualified-id-join-indexing-handler.cc @@ -21,7 +21,7 @@ #include "icing/text_classifier/lib3/utils/base/statusor.h" #include "icing/absl_ports/canonical_errors.h" #include "icing/join/doc-join-info.h" -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/join/qualified-id.h" #include "icing/legacy/core/icing-string-util.h" #include "icing/proto/logging.pb.h" @@ -38,7 +38,7 @@ namespace lib { /* static */ libtextclassifier3::StatusOr< std::unique_ptr<QualifiedIdJoinIndexingHandler>> QualifiedIdJoinIndexingHandler::Create( - const Clock* clock, QualifiedIdTypeJoinableIndex* qualified_id_join_index) { + const Clock* clock, QualifiedIdJoinIndex* qualified_id_join_index) { ICING_RETURN_ERROR_IF_NULL(clock); ICING_RETURN_ERROR_IF_NULL(qualified_id_join_index); diff --git a/icing/join/qualified-id-join-indexing-handler.h b/icing/join/qualified-id-join-indexing-handler.h index 434403e..f44e45d 100644 --- a/icing/join/qualified-id-join-indexing-handler.h +++ b/icing/join/qualified-id-join-indexing-handler.h @@ -17,7 +17,7 @@ #include "icing/text_classifier/lib3/utils/base/status.h" #include "icing/index/data-indexing-handler.h" -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/proto/logging.pb.h" #include "icing/store/document-id.h" #include "icing/util/clock.h" @@ -37,13 +37,12 @@ class QualifiedIdJoinIndexingHandler : public DataIndexingHandler { // - FAILED_PRECONDITION_ERROR if any of the input pointer is null static libtextclassifier3::StatusOr< std::unique_ptr<QualifiedIdJoinIndexingHandler>> - Create(const Clock* clock, - QualifiedIdTypeJoinableIndex* qualified_id_join_index); + Create(const Clock* clock, QualifiedIdJoinIndex* qualified_id_join_index); ~QualifiedIdJoinIndexingHandler() override = default; // Handles the joinable qualified id data indexing process: add data into the - // qualified id type joinable cache. + // qualified id join index. // /// Returns: // - OK on success. @@ -51,18 +50,18 @@ class QualifiedIdJoinIndexingHandler : public DataIndexingHandler { // than or equal to the document_id of a previously indexed document in // non recovery mode. // - INTERNAL_ERROR if any other errors occur. - // - Any QualifiedIdTypeJoinableIndex errors. + // - Any QualifiedIdJoinIndex errors. libtextclassifier3::Status Handle( const TokenizedDocument& tokenized_document, DocumentId document_id, bool recovery_mode, PutDocumentStatsProto* put_document_stats) override; private: explicit QualifiedIdJoinIndexingHandler( - const Clock* clock, QualifiedIdTypeJoinableIndex* qualified_id_join_index) + const Clock* clock, QualifiedIdJoinIndex* qualified_id_join_index) : DataIndexingHandler(clock), qualified_id_join_index_(*qualified_id_join_index) {} - QualifiedIdTypeJoinableIndex& qualified_id_join_index_; // Does not own. + QualifiedIdJoinIndex& qualified_id_join_index_; // Does not own. }; } // namespace lib diff --git a/icing/join/qualified-id-join-indexing-handler_test.cc b/icing/join/qualified-id-join-indexing-handler_test.cc index e48dc33..7e89dfa 100644 --- a/icing/join/qualified-id-join-indexing-handler_test.cc +++ b/icing/join/qualified-id-join-indexing-handler_test.cc @@ -23,7 +23,7 @@ #include "gtest/gtest.h" #include "icing/document-builder.h" #include "icing/file/filesystem.h" -#include "icing/join/qualified-id-type-joinable-index.h" +#include "icing/join/qualified-id-join-index.h" #include "icing/join/qualified-id.h" #include "icing/portable/platform.h" #include "icing/proto/document.pb.h" @@ -92,9 +92,9 @@ class QualifiedIdJoinIndexingHandlerTest : public ::testing::Test { ICING_ASSERT_OK_AND_ASSIGN( qualified_id_join_index_, - QualifiedIdTypeJoinableIndex::Create( - filesystem_, qualified_id_join_index_dir_, - /*pre_mapping_fbv=*/false, /*use_persistent_hash_map=*/false)); + QualifiedIdJoinIndex::Create(filesystem_, qualified_id_join_index_dir_, + /*pre_mapping_fbv=*/false, + /*use_persistent_hash_map=*/false)); language_segmenter_factory::SegmenterOptions segmenter_options(ULOC_US); ICING_ASSERT_OK_AND_ASSIGN( @@ -156,7 +156,7 @@ class QualifiedIdJoinIndexingHandlerTest : public ::testing::Test { std::string qualified_id_join_index_dir_; std::string schema_store_dir_; - std::unique_ptr<QualifiedIdTypeJoinableIndex> qualified_id_join_index_; + std::unique_ptr<QualifiedIdJoinIndex> qualified_id_join_index_; std::unique_ptr<LanguageSegmenter> lang_segmenter_; std::unique_ptr<SchemaStore> schema_store_; }; diff --git a/icing/performance-configuration.cc b/icing/performance-configuration.cc index 07ff9bc..1518381 100644 --- a/icing/performance-configuration.cc +++ b/icing/performance-configuration.cc @@ -38,20 +38,17 @@ namespace { // rendering 2 frames. // // With the information above, we then try to choose default values for -// query_length and num_to_score so that the overall time can comfortably fit -// in with our goal. +// query_length so that the overall time can comfortably fit in with our goal +// (note that num_to_score will be decided by the client, which is specified in +// ResultSpecProto). // 1. Set query_length to 23000 so that any query can be executed by // QueryProcessor within 15 ms on a Pixel 3 XL according to results of // //icing/query:query-processor_benchmark. -// 2. Set num_to_score to 30000 so that results can be scored and ranked within -// 3 ms on a Pixel 3 XL according to results of -// //icing/scoring:score-and-rank_benchmark. // // In the worse-case scenario, we still have [33 ms - 15 ms - 3 ms] = 15 ms left // for all the other things like proto parsing, document fetching, and even // Android Binder calls if Icing search engine runs in a separate process. constexpr int kMaxQueryLength = 23000; -constexpr int kDefaultNumToScore = 30000; // New Android devices nowadays all allow more than 16 MB memory per app. Using // that as a guideline and being more conservative, we set 4 MB as the safe @@ -67,8 +64,7 @@ constexpr int kMaxNumTotalHits = kSafeMemoryUsage / sizeof(ScoredDocumentHit); } // namespace PerformanceConfiguration::PerformanceConfiguration() - : PerformanceConfiguration(kMaxQueryLength, kDefaultNumToScore, - kMaxNumTotalHits) {} + : PerformanceConfiguration(kMaxQueryLength, kMaxNumTotalHits) {} } // namespace lib } // namespace icing diff --git a/icing/performance-configuration.h b/icing/performance-configuration.h index b9282ca..3ec67f3 100644 --- a/icing/performance-configuration.h +++ b/icing/performance-configuration.h @@ -23,10 +23,8 @@ struct PerformanceConfiguration { // Loads default configuration. PerformanceConfiguration(); - PerformanceConfiguration(int max_query_length_in, int num_to_score_in, - int max_num_total_hits) + PerformanceConfiguration(int max_query_length_in, int max_num_total_hits) : max_query_length(max_query_length_in), - num_to_score(num_to_score_in), max_num_total_hits(max_num_total_hits) {} // Search performance @@ -34,9 +32,6 @@ struct PerformanceConfiguration { // Maximum length of query to execute in IndexProcessor. int max_query_length; - // Number of results to score in ScoringProcessor for every query. - int num_to_score; - // Memory // Maximum number of ScoredDocumentHits to cache in the ResultStateManager at diff --git a/icing/schema-builder.h b/icing/schema-builder.h index e8be483..c74505e 100644 --- a/icing/schema-builder.h +++ b/icing/schema-builder.h @@ -127,6 +127,22 @@ class PropertyConfigBuilder { property_.set_schema_type(std::string(schema_type)); property_.mutable_document_indexing_config()->set_index_nested_properties( index_nested_properties); + property_.mutable_document_indexing_config() + ->clear_indexable_nested_properties_list(); + return *this; + } + + PropertyConfigBuilder& SetDataTypeDocument( + std::string_view schema_type, + std::initializer_list<std::string> indexable_nested_properties_list) { + property_.set_data_type(PropertyConfigProto::DataType::DOCUMENT); + property_.set_schema_type(std::string(schema_type)); + property_.mutable_document_indexing_config()->set_index_nested_properties( + false); + for (const std::string& property : indexable_nested_properties_list) { + property_.mutable_document_indexing_config() + ->add_indexable_nested_properties_list(property); + } return *this; } diff --git a/icing/schema/property-util.cc b/icing/schema/property-util.cc index 7370328..67ff748 100644 --- a/icing/schema/property-util.cc +++ b/icing/schema/property-util.cc @@ -16,11 +16,9 @@ #include <string> #include <string_view> -#include <utility> #include <vector> #include "icing/text_classifier/lib3/utils/base/statusor.h" -#include "icing/absl_ports/canonical_errors.h" #include "icing/absl_ports/str_cat.h" #include "icing/absl_ports/str_join.h" #include "icing/proto/document.pb.h" @@ -85,6 +83,23 @@ std::vector<PropertyInfo> ParsePropertyPathExpr( return property_infos; } +bool IsParentPropertyPath(std::string_view property_path_expr1, + std::string_view property_path_expr2) { + if (property_path_expr2.length() < property_path_expr1.length()) { + return false; + } + if (property_path_expr1 != + property_path_expr2.substr(0, property_path_expr1.length())) { + return false; + } + if (property_path_expr2.length() > property_path_expr1.length() && + property_path_expr2[property_path_expr1.length()] != + kPropertyPathSeparator[0]) { + return false; + } + return true; +} + const PropertyProto* GetPropertyProto(const DocumentProto& document, std::string_view property_name) { for (const PropertyProto& property : document.properties()) { diff --git a/icing/schema/property-util.h b/icing/schema/property-util.h index efa599c..7557879 100644 --- a/icing/schema/property-util.h +++ b/icing/schema/property-util.h @@ -113,6 +113,26 @@ PropertyInfo ParsePropertyNameExpr(std::string_view property_name_expr); std::vector<PropertyInfo> ParsePropertyPathExpr( std::string_view property_path_expr); +// A property path property_path_expr1 is considered a parent of another +// property path property_path_expr2 if: +// 1. property_path_expr2 == property_path_expr1, OR +// 2. property_path_expr2 consists of the entire path of property_path_expr1 +// + "." + [some other property path]. +// +// Note that this can only be used for property name strings that do not +// contain the property index. +// +// Examples: +// - IsParentPropertyPath("foo", "foo") will return true. +// - IsParentPropertyPath("foo", "foo.bar") will return true. +// - IsParentPropertyPath("foo", "bar.foo") will return false. +// - IsParentPropertyPath("foo.bar", "foo.foo.bar") will return false. +// +// Returns: true if property_path_expr1 is a parent property path of +// property_path_expr2. +bool IsParentPropertyPath(std::string_view property_path_expr1, + std::string_view property_path_expr2); + // Gets the desired PropertyProto from the document by given property name. // Since the input parameter is property name, this function only deals with // the first level of properties in the document and cannot deal with nested diff --git a/icing/schema/property-util_test.cc b/icing/schema/property-util_test.cc index 1fabb32..eddcc84 100644 --- a/icing/schema/property-util_test.cc +++ b/icing/schema/property-util_test.cc @@ -43,6 +43,23 @@ static constexpr std::string_view kTypeNestedTest = "NestedTest"; static constexpr std::string_view kPropertyStr = "str"; static constexpr std::string_view kPropertyNestedDocument = "nestedDocument"; +TEST(PropertyUtilTest, IsParentPropertyPath) { + EXPECT_TRUE(property_util::IsParentPropertyPath("foo", "foo")); + EXPECT_TRUE(property_util::IsParentPropertyPath("foo", "foo.bar")); + EXPECT_TRUE(property_util::IsParentPropertyPath("foo", "foo.bar.foo")); + EXPECT_TRUE(property_util::IsParentPropertyPath("foo", "foo.foo.bar")); + EXPECT_TRUE(property_util::IsParentPropertyPath("foo.bar", "foo.bar.foo")); + + EXPECT_FALSE(property_util::IsParentPropertyPath("foo", "foofoo.bar")); + EXPECT_FALSE(property_util::IsParentPropertyPath("foo.bar", "foo.foo.bar")); + EXPECT_FALSE(property_util::IsParentPropertyPath("foo.bar", "foofoo.bar")); + EXPECT_FALSE(property_util::IsParentPropertyPath("foo.bar.foo", "foo")); + EXPECT_FALSE(property_util::IsParentPropertyPath("foo.bar.foo", "foo.bar")); + EXPECT_FALSE( + property_util::IsParentPropertyPath("foo.foo.bar", "foo.bar.foo")); + EXPECT_FALSE(property_util::IsParentPropertyPath("foo", "foo#bar.foo")); +} + TEST(PropertyUtilTest, ExtractPropertyValuesTypeString) { PropertyProto property; property.mutable_string_values()->Add("Hello, world"); diff --git a/icing/schema/schema-property-iterator.cc b/icing/schema/schema-property-iterator.cc index e1078c2..2d1ca7e 100644 --- a/icing/schema/schema-property-iterator.cc +++ b/icing/schema/schema-property-iterator.cc @@ -14,9 +14,17 @@ #include "icing/schema/schema-property-iterator.h" +#include <algorithm> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + #include "icing/text_classifier/lib3/utils/base/status.h" #include "icing/absl_ports/canonical_errors.h" #include "icing/absl_ports/str_cat.h" +#include "icing/proto/schema.pb.h" +#include "icing/schema/property-util.h" namespace icing { namespace lib { @@ -27,16 +35,56 @@ libtextclassifier3::Status SchemaPropertyIterator::Advance() { // When finishing iterating all properties of the current level, pop it // from the stack (levels_), return to the previous level and resume the // iteration. - parent_type_config_names_.erase(levels_.back().GetSchemaTypeName()); + parent_type_config_names_.erase( + parent_type_config_names_.find(levels_.back().GetSchemaTypeName())); levels_.pop_back(); continue; } const PropertyConfigProto& curr_property_config = levels_.back().GetCurrentPropertyConfig(); + std::string curr_property_path = levels_.back().GetCurrentPropertyPath(); + + // Iterate through the sorted_top_level_indexable_nested_properties_ in + // order until we find the first element that is >= curr_property_path. + while (current_top_level_indexable_nested_properties_idx_ < + sorted_top_level_indexable_nested_properties_.size() && + sorted_top_level_indexable_nested_properties_.at( + current_top_level_indexable_nested_properties_idx_) < + curr_property_path) { + // If an element in sorted_top_level_indexable_nested_properties_ < the + // current property path, it means that we've already iterated past the + // possible position for it without seeing it. + // It's not a valid property path in our schema definition. Ignore it and + // advance current_top_level_indexable_nested_properties_idx_. + ++current_top_level_indexable_nested_properties_idx_; + } + if (curr_property_config.data_type() != PropertyConfigProto::DataType::DOCUMENT) { // We've advanced to a leaf property. + // Set whether this property is indexable according to its level's + // indexable config. If this property is declared in + // indexable_nested_properties_list of the top-level schema, it is also + // nested indexable. + std::string* current_indexable_nested_prop = + current_top_level_indexable_nested_properties_idx_ < + sorted_top_level_indexable_nested_properties_.size() + ? &sorted_top_level_indexable_nested_properties_.at( + current_top_level_indexable_nested_properties_idx_) + : nullptr; + if (current_indexable_nested_prop == nullptr || + *current_indexable_nested_prop > curr_property_path) { + // Current property is not in the indexable list. Set its indexable + // config according to the current level's indexable config. + levels_.back().SetCurrentPropertyIndexable( + levels_.back().GetLevelNestedIndexable()); + } else if (*current_indexable_nested_prop == curr_property_path) { + // Current property is in the indexable list. Set its indexable config + // to true. + levels_.back().SetCurrentPropertyIndexable(true); + ++current_top_level_indexable_nested_properties_idx_; + } return libtextclassifier3::Status::OK; } @@ -55,27 +103,71 @@ libtextclassifier3::Status SchemaPropertyIterator::Advance() { return absl_ports::NotFoundError(absl_ports::StrCat( "Type config not found: ", curr_property_config.schema_type())); } + const SchemaTypeConfigProto& nested_type_config = + nested_type_config_iter->second; + + if (levels_.back().GetLevelNestedIndexable()) { + // We should set sorted_top_level_indexable_nested_properties_ to the list + // defined by the current level. + // GetLevelNestedIndexable() is true either because: + // 1. We're looking at a document property of the top-level schema -- + // The first LevelInfo for the iterator is initialized with + // all_nested_properties_indexable_ = true. + // 2. All previous levels set index_nested_properties = true: + // This indicates that upper-level schema types want to follow nested + // properties definition of its document subtypes. If this is the first + // subtype level that defines a list, we should set it as + // top_level_indexable_nested_properties_ for the current top-level + // schema. + sorted_top_level_indexable_nested_properties_.clear(); + sorted_top_level_indexable_nested_properties_.reserve( + curr_property_config.document_indexing_config() + .indexable_nested_properties_list() + .size()); + for (const std::string& property : + curr_property_config.document_indexing_config() + .indexable_nested_properties_list()) { + // Concat the current property name to each property to get the full + // property path expression for each indexable nested property. + sorted_top_level_indexable_nested_properties_.push_back( + property_util::ConcatenatePropertyPathExpr(curr_property_path, + property)); + } + current_top_level_indexable_nested_properties_idx_ = 0; + std::sort(sorted_top_level_indexable_nested_properties_.begin(), + sorted_top_level_indexable_nested_properties_.end()); + } - if (parent_type_config_names_.count( - nested_type_config_iter->second.schema_type()) > 0) { + bool is_cycle = + parent_type_config_names_.find(nested_type_config.schema_type()) != + parent_type_config_names_.end(); + bool is_parent_property_path = + current_top_level_indexable_nested_properties_idx_ < + sorted_top_level_indexable_nested_properties_.size() && + property_util::IsParentPropertyPath( + curr_property_path, + sorted_top_level_indexable_nested_properties_.at( + current_top_level_indexable_nested_properties_idx_)); + if (is_cycle && !is_parent_property_path) { // Cycle detected. The schema definition is guaranteed to be valid here // since it must have already been validated during SchemaUtil::Validate, // which would have rejected any schema with bad cycles. // + // There are no properties in the indexable_nested_properties_list that + // are a part of this circular reference. // We do not need to iterate this type further so we simply move on to // other properties in the parent type. continue; } - std::string curr_property_path = levels_.back().GetCurrentPropertyPath(); - bool is_nested_indexable = levels_.back().GetCurrentNestedIndexable() && - curr_property_config.document_indexing_config() - .index_nested_properties(); - levels_.push_back(LevelInfo(nested_type_config_iter->second, + bool all_nested_properties_indexable = + levels_.back().GetLevelNestedIndexable() && + curr_property_config.document_indexing_config() + .index_nested_properties(); + levels_.push_back(LevelInfo(nested_type_config, std::move(curr_property_path), - is_nested_indexable)); - parent_type_config_names_.insert( - nested_type_config_iter->second.schema_type()); + all_nested_properties_indexable)); + parent_type_config_names_.insert(nested_type_config.schema_type()); } return absl_ports::OutOfRangeError("End of iterator"); } diff --git a/icing/schema/schema-property-iterator.h b/icing/schema/schema-property-iterator.h index f60a56e..3babf9e 100644 --- a/icing/schema/schema-property-iterator.h +++ b/icing/schema/schema-property-iterator.h @@ -18,6 +18,9 @@ #include <algorithm> #include <numeric> #include <string> +#include <string_view> +#include <unordered_set> +#include <utility> #include <vector> #include "icing/text_classifier/lib3/utils/base/status.h" @@ -44,7 +47,7 @@ class SchemaPropertyIterator { : type_config_map_(type_config_map) { levels_.push_back(LevelInfo(base_schema_type_config, /*base_property_path=*/"", - /*is_nested_indexable=*/true)); + /*all_nested_properties_indexable=*/true)); parent_type_config_names_.insert(base_schema_type_config.schema_type()); } @@ -62,11 +65,22 @@ class SchemaPropertyIterator { return levels_.back().GetCurrentPropertyPath(); } - // Gets if the current property is nested indexable. + // Returns whether the current property is indexable. This would be true if + // either the current level is nested indexable, or if the current property is + // declared indexable in the indexable_nested_properties_list of the top-level + // schema type. // // REQUIRES: The preceding call for Advance() is OK. - bool GetCurrentNestedIndexable() const { - return levels_.back().GetCurrentNestedIndexable(); + bool GetCurrentPropertyIndexable() const { + return levels_.back().GetCurrentPropertyIndexable(); + } + + // Returns whether the current schema level is nested indexable. If this is + // true, all properties in the level are indexed. + // + // REQUIRES: The preceding call for Advance() is OK. + bool GetLevelNestedIndexable() const { + return levels_.back().GetLevelNestedIndexable(); } // Advances to the next leaf property. @@ -87,12 +101,14 @@ class SchemaPropertyIterator { class LevelInfo { public: explicit LevelInfo(const SchemaTypeConfigProto& schema_type_config, - std::string base_property_path, bool is_nested_indexable) + std::string base_property_path, + bool all_nested_properties_indexable) : schema_type_config_(schema_type_config), base_property_path_(std::move(base_property_path)), sorted_property_indices_(schema_type_config.properties_size()), current_vec_idx_(-1), - is_nested_indexable_(is_nested_indexable) { + sorted_property_indexable_(schema_type_config.properties_size()), + all_nested_properties_indexable_(all_nested_properties_indexable) { // Index sort property by lexicographical order. std::iota(sorted_property_indices_.begin(), sorted_property_indices_.end(), @@ -119,7 +135,17 @@ class SchemaPropertyIterator { base_property_path_, GetCurrentPropertyConfig().property_name()); } - bool GetCurrentNestedIndexable() const { return is_nested_indexable_; } + bool GetLevelNestedIndexable() const { + return all_nested_properties_indexable_; + } + + bool GetCurrentPropertyIndexable() const { + return sorted_property_indexable_[current_vec_idx_]; + } + + void SetCurrentPropertyIndexable(bool indexable) { + sorted_property_indexable_[current_vec_idx_] = indexable; + } std::string_view GetSchemaTypeName() const { return schema_type_config_.schema_type(); @@ -137,12 +163,20 @@ class SchemaPropertyIterator { std::vector<int> sorted_property_indices_; int current_vec_idx_; - // Indicates if the current level is nested indexable. Document type - // property has index_nested_properties flag indicating whether properties - // under this level should be indexed or not. If any of parent document type - // property sets its flag false, then all child level properties should not - // be indexed. - bool is_nested_indexable_; + // Vector indicating whether each property in the current level is + // indexable. We can declare different indexable settings for properties in + // the same level using indexable_nested_properties_list. + // + // Element indices in this vector correspond to property indices in the + // sorted order. + std::vector<bool> sorted_property_indexable_; + + // Indicates if all properties in the current level is nested indexable. + // This would be true for a level if the document declares + // index_nested_properties=true. If any of parent document type + // property sets its flag false, then this would be false for all its child + // properties. + bool all_nested_properties_indexable_; }; const SchemaUtil::TypeConfigMap& type_config_map_; // Does not own @@ -154,7 +188,14 @@ class SchemaPropertyIterator { // Maintaining all traversed parent schema type config names of the current // stack (levels_). It is used to detect nested schema cycle dependency. - std::unordered_set<std::string_view> parent_type_config_names_; + std::unordered_multiset<std::string_view> parent_type_config_names_; + + // Sorted list of indexable nested properties for the top-level schema. + std::vector<std::string> sorted_top_level_indexable_nested_properties_; + + // Current iteration index in the sorted_top_level_indexable_nested_properties + // list. + int current_top_level_indexable_nested_properties_idx_ = 0; }; } // namespace lib diff --git a/icing/schema/schema-property-iterator_test.cc b/icing/schema/schema-property-iterator_test.cc index 080d574..ed71134 100644 --- a/icing/schema/schema-property-iterator_test.cc +++ b/icing/schema/schema-property-iterator_test.cc @@ -14,6 +14,7 @@ #include "icing/schema/schema-property-iterator.h" +#include <initializer_list> #include <string> #include "icing/text_classifier/lib3/utils/base/status.h" @@ -57,19 +58,19 @@ TEST(SchemaPropertyIteratorTest, EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Alphabet")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config.properties(2))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Youtube")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -139,49 +140,49 @@ TEST(SchemaPropertyIteratorTest, EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Hello")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config3.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Icing.Bar.Alphabet")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(2))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Icing.Bar.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Icing.Bar.Youtube")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Icing.Foo")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config2.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("World.Alphabet")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(2))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("World.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("World.Youtube")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -338,13 +339,13 @@ TEST(SchemaPropertyIteratorTest, NestedIndexable) { EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz1.Bar.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz1.Foo")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config2.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); // For Baz2, the parent level sets index_nested_properties = false, so all // leaf properties in child levels should be nested unindexable even if @@ -353,13 +354,13 @@ TEST(SchemaPropertyIteratorTest, NestedIndexable) { EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz2.Bar.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz2.Foo")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config2.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsFalse()); // For Baz3, the parent level sets index_nested_properties = true, but the // child level sets index_nested_properties = false. @@ -369,13 +370,13 @@ TEST(SchemaPropertyIteratorTest, NestedIndexable) { EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz3.Bar.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz3.Foo")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config2.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); // For Baz4, all levels set index_nested_properties = false, so all leaf // properties should be nested unindexable. @@ -383,37 +384,954 @@ TEST(SchemaPropertyIteratorTest, NestedIndexable) { EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz4.Bar.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Baz4.Foo")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config2.properties(1))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsFalse()); // Verify 1 and 0 level of nested document type properties. EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Hello1.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("Hello2.Google")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config1.properties(0))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(iterator.Advance(), IsOk()); EXPECT_THAT(iterator.GetCurrentPropertyPath(), Eq("World")); EXPECT_THAT(iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config4.properties(6))); - EXPECT_THAT(iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); } +TEST(SchemaPropertyIteratorTest, + IndexableNestedPropertiesList_singleNestedLevel) { + std::string schema_type_name1 = "SchemaOne"; + std::string schema_type_name2 = "SchemaTwo"; + + SchemaTypeConfigProto schema_type_config1 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name1) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop2") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop3") + .SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)) + .Build(); + SchemaTypeConfigProto schema_type_config2 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name2) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop1") + .SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/{"schema1prop2", + "schema1prop3"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop3") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaUtil::TypeConfigMap type_config_map = { + {schema_type_name1, schema_type_config1}, + {schema_type_name2, schema_type_config2}}; + + // Order of iteration for Schema2: + // {"schema2prop1.schema1prop1", "schema2prop1.schema1prop2", + // "schema2prop1.schema1prop3", "schema2prop2", "schema2prop3"} + // + // Indexable properties: + // {"schema2prop1.schema1prop2", "schema2prop1.schema1prop3", "schema2prop2", + // "schema2prop3"} + SchemaPropertyIterator schema2_iterator(schema_type_config2, type_config_map); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop1")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("schema2prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("schema2prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, + IndexableNestedPropertiesList_indexBooleanTrueDoesNotAffectOtherLevels) { + std::string schema_type_name1 = "SchemaOne"; + std::string schema_type_name2 = "SchemaTwo"; + std::string schema_type_name3 = "SchemaThree"; + + SchemaTypeConfigProto schema_type_config1 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name1) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop2") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop3") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config2 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name2) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop1") + .SetDataTypeDocument(schema_type_name1, + /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop3") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config3 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name3) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema3prop3") + .SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/{"schema1prop1", + "schema1prop3"})) + .AddProperty(PropertyConfigBuilder() + .SetName("schema3prop1") + .SetDataTypeDocument( + schema_type_name2, + /*indexable_nested_properties_list=*/ + {"schema2prop2", "schema2prop1.schema1prop1", + "schema2prop1.schema1prop3"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema3prop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaUtil::TypeConfigMap type_config_map = { + {schema_type_name1, schema_type_config1}, + {schema_type_name2, schema_type_config2}, + {schema_type_name3, schema_type_config3}}; + + // Order of iteration for Schema3: + // {"schema3prop1.schema2prop1.schema1prop1", + // "schema3prop1.schema2prop1.schema1prop2", + // "schema3prop1.schema2prop1.schema1prop3", + // "schema3prop1.schema2prop2", "schema3prop1.schema2prop3", "schema3prop2", + // "schema3prop3.schema1prop1", "schema3prop3.schema1prop2", + // "schema3prop3.schema1prop3"}. + // + // Indexable properties: + // {"schema3prop1.schema2prop1.schema1prop1", + // "schema3prop1.schema2prop1.schema1prop3", + // "schema3prop1.schema2prop2", "schema3prop2", "schema3prop3.schema1prop1", + // "schema3prop3.schema1prop3"} + // + // Schema2 setting index_nested_properties=true does not affect nested + // properties indexing for Schema3. + SchemaPropertyIterator schema3_iterator(schema_type_config3, type_config_map); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("schema3prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config3.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop3.schema1prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop3.schema1prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop3.schema1prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for Schema2: + // {"schema2prop1.schema1prop1", "schema2prop1.schema1prop2", + // "schema2prop1.schema1prop3", "schema2prop2", "schema2prop3"} + // + // Indexable properties: + // {"schema2prop1.schema1prop1", "schema2prop1.schema1prop2", + // "schema2prop1.schema1prop3", "schema2prop2", "schema2prop3"} + // + // All properties are indexed because index_nested_properties=true for + // Schema2.schema2prop1. Schema3's indexable_nested_properties setting does + // not affect this. + SchemaPropertyIterator schema2_iterator(schema_type_config2, type_config_map); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop1")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("schema2prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("schema2prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, + IndexableNestedPropertiesList_indexBooleanFalseDoesNotAffectOtherLevels) { + std::string schema_type_name1 = "SchemaOne"; + std::string schema_type_name2 = "SchemaTwo"; + std::string schema_type_name3 = "SchemaThree"; + + SchemaTypeConfigProto schema_type_config1 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name1) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop2") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config2 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name2) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop1") + .SetDataTypeDocument(schema_type_name1, + /*index_nested_properties=*/false)) + .Build(); + SchemaTypeConfigProto schema_type_config3 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name3) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema3prop1") + .SetDataTypeDocument(schema_type_name2, + /*indexable_nested_properties_list=*/ + std::initializer_list<std::string>{ + "schema2prop1.schema1prop2"})) + .Build(); + SchemaUtil::TypeConfigMap type_config_map = { + {schema_type_name1, schema_type_config1}, + {schema_type_name2, schema_type_config2}, + {schema_type_name3, schema_type_config3}}; + + // Order of iteration for Schema3: + // {"schema3prop1.schema2prop1.schema1prop1", + // "schema3prop1.schema2prop1.schema1prop2"}. + // + // Indexable properties: {"schema3prop1.schema2prop1.schema1prop2"} + // + // Schema2 setting index_nested_properties=false, does not affect Schema3's + // indexable list. + SchemaPropertyIterator schema3_iterator(schema_type_config3, type_config_map); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for Schema2: + // {"schema2prop1.schema1prop1", "schema2prop1.schema1prop2"} + // + // Indexable properties: None + // + // The indexable list for Schema3 does not propagate to Schema2. + SchemaPropertyIterator schema2_iterator(schema_type_config2, type_config_map); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop1")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, + IndexableNestedPropertiesList_indexableSetDoesNotAffectOtherLevels) { + std::string schema_type_name1 = "SchemaOne"; + std::string schema_type_name2 = "SchemaTwo"; + std::string schema_type_name3 = "SchemaThree"; + + SchemaTypeConfigProto schema_type_config1 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name1) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop2") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop3") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config2 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name2) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop1") + .SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/ + std::initializer_list<std::string>{"schema1prop2"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop3") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config3 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name3) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema3prop3") + .SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/{"schema1prop1", + "schema1prop3"})) + .AddProperty(PropertyConfigBuilder() + .SetName("schema3prop1") + .SetDataTypeDocument( + schema_type_name2, + /*indexable_nested_properties_list=*/ + {"schema2prop2", "schema2prop1.schema1prop1", + "schema2prop1.schema1prop3"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema3prop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaUtil::TypeConfigMap type_config_map = { + {schema_type_name1, schema_type_config1}, + {schema_type_name2, schema_type_config2}, + {schema_type_name3, schema_type_config3}}; + + // Order of iteration for Schema3: + // {"schema3prop1.schema2prop1.schema1prop1", + // "schema3prop1.schema2prop1.schema1prop2", + // "schema3prop1.schema2prop1.schema1prop3", + // "schema3prop1.schema2prop2", "schema3prop1.schema2prop3", "schema3prop2", + // "schema3prop3.schema1prop1", "schema3prop3.schema1prop2", + // "schema3prop3.schema1prop3"}. + // + // Indexable properties: + // {"schema3prop1.schema2prop1.schema1prop1", + // "schema3prop1.schema2prop1.schema1prop3", + // "schema3prop1.schema2prop2", "schema3prop2", "schema3prop3.schema1prop1", + // "schema3prop3.schema1prop3"} + // + // Schema2 setting indexable_nested_properties_list={schema1prop2} does not + // affect nested properties indexing for Schema3. + SchemaPropertyIterator schema3_iterator(schema_type_config3, type_config_map); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("schema3prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config3.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop3.schema1prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop3.schema1prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop3.schema1prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for Schema2: + // {"schema2prop1.schema1prop1", "schema2prop1.schema1prop2", + // "schema2prop1.schema1prop3", "schema2prop2", "schema2prop3"} + // + // Indexable properties: + // {"schema2prop1.schema1prop2", "schema2prop2", "schema2prop3"} + // + // Indexable_nested_properties set for Schema3.schema3prop1 does not propagate + // to Schema2. + SchemaPropertyIterator schema2_iterator(schema_type_config2, type_config_map); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop1")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("schema2prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("schema2prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST( + SchemaPropertyIteratorTest, + IndexableNestedPropertiesList_upperLevelIndexTrueIndexesListOfNestedLevel) { + std::string schema_type_name1 = "SchemaOne"; + std::string schema_type_name2 = "SchemaTwo"; + std::string schema_type_name3 = "SchemaThree"; + std::string schema_type_name4 = "SchemaFour"; + + SchemaTypeConfigProto schema_type_config1 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name1) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema1prop2") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config2 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name2) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema2prop1") + .SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/ + std::initializer_list<std::string>{"schema1prop2"})) + .Build(); + SchemaTypeConfigProto schema_type_config3 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name3) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema3prop1") + .SetDataTypeDocument(schema_type_name2, + /*index_nested_properties=*/true)) + .Build(); + SchemaTypeConfigProto schema_type_config4 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name4) + .AddProperty( + PropertyConfigBuilder() + .SetName("schema4prop1") + .SetDataTypeDocument(schema_type_name3, + /*index_nested_properties=*/true)) + .Build(); + SchemaUtil::TypeConfigMap type_config_map = { + {schema_type_name1, schema_type_config1}, + {schema_type_name2, schema_type_config2}, + {schema_type_name3, schema_type_config3}, + {schema_type_name4, schema_type_config4}}; + + // Order of iteration for Schema4: + // {"schema4prop1.schema3prop1.schema2prop1.schema1prop1", + // "schema4prop1.schema3prop1.schema2prop1.schema1prop2"}. + // + // Indexable properties: {schema4prop1.schema3prop1.schema2prop1.schema1prop2} + // + // Both Schema4 and Schema3 sets index_nested_properties=true, so they both + // want to follow the indexing behavior of its subtype. + // Schema2 is the first subtype to define an indexing config, so we index its + // list for both Schema3 and Schema4 even though it sets + // index_nested_properties=false. + SchemaPropertyIterator schema4_iterator(schema_type_config4, type_config_map); + + EXPECT_THAT(schema4_iterator.Advance(), IsOk()); + EXPECT_THAT(schema4_iterator.GetCurrentPropertyPath(), + Eq("schema4prop1.schema3prop1.schema2prop1.schema1prop1")); + EXPECT_THAT(schema4_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema4_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema4_iterator.Advance(), IsOk()); + EXPECT_THAT(schema4_iterator.GetCurrentPropertyPath(), + Eq("schema4prop1.schema3prop1.schema2prop1.schema1prop2")); + EXPECT_THAT(schema4_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema4_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema4_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for Schema3: + // {"schema3prop1.schema2prop1.schema1prop1", + // "schema3prop1.schema2prop1.schema1prop2"}. + // + // Indexable properties: {schema3prop1.schema2prop1.schema1prop2} + SchemaPropertyIterator schema3_iterator(schema_type_config3, type_config_map); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("schema3prop1.schema2prop1.schema1prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for Schema2: + // {"schema2prop1.schema1prop1", "schema2prop1.schema1prop2"} + // + // Indexable properties: + // {"schema2prop1.schema1prop2"} + // + // Schema3 setting index_nested_properties=true does not propagate to Schema2. + SchemaPropertyIterator schema2_iterator(schema_type_config2, type_config_map); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop1")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), + Eq("schema2prop1.schema1prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, + IndexableNestedProperties_duplicatePropertyNamesInDifferentProperties) { + std::string schema_type_name1 = "SchemaOne"; + std::string schema_type_name2 = "SchemaTwo"; + std::string schema_type_name3 = "SchemaThree"; + + SchemaTypeConfigProto schema_type_config1 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name1) + .AddProperty( + PropertyConfigBuilder().SetName("prop1").SetDataTypeString( + TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder().SetName("prop2").SetDataTypeString( + TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder().SetName("prop3").SetDataTypeString( + TERM_MATCH_PREFIX, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config2 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name2) + .AddProperty( + PropertyConfigBuilder().SetName("prop1").SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/ + std::initializer_list<std::string>{"prop2"})) + .AddProperty( + PropertyConfigBuilder().SetName("prop2").SetDataTypeString( + TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder().SetName("prop3").SetDataTypeString( + TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config3 = + SchemaTypeConfigBuilder() + .SetType(schema_type_name3) + .AddProperty( + PropertyConfigBuilder().SetName("prop3").SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/ + {"prop1", "prop3"})) + .AddProperty( + PropertyConfigBuilder().SetName("prop1").SetDataTypeDocument( + schema_type_name2, + /*indexable_nested_properties_list=*/ + {"prop2", "prop1.prop1", "prop1.prop3"})) + .AddProperty( + PropertyConfigBuilder().SetName("prop2").SetDataTypeString( + TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder().SetName("prop4").SetDataTypeDocument( + schema_type_name1, + /*indexable_nested_properties_list=*/ + {"prop2", "prop3"})) + .Build(); + SchemaUtil::TypeConfigMap type_config_map = { + {schema_type_name1, schema_type_config1}, + {schema_type_name2, schema_type_config2}, + {schema_type_name3, schema_type_config3}}; + + // Order of iteration for Schema3: + // {"prop1.prop1.prop1", "prop1.prop1.prop2", "prop1.prop1.prop3", + // "prop1.prop2", "prop1.prop3", "prop2", + // "prop3.prop1", "prop3.prop2", "prop3.prop3", + // "prop4.prop1", "prop4.prop2", "prop4.prop3"}. + // + // Indexable properties: + // {"prop1.prop1.prop1", "prop1.prop1.prop3", "prop1.prop2", "prop2", + // "prop3.prop1", "prop3.prop3", "prop4.prop2", "prop4.prop3"} + // + // Properties do not affect other properties with the same name from different + // properties. + SchemaPropertyIterator schema3_iterator(schema_type_config3, type_config_map); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("prop1.prop1.prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("prop1.prop1.prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), + Eq("prop1.prop1.prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop1.prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop1.prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config3.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop3.prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop3.prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop3.prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop4.prop1")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop4.prop2")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), IsOk()); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyPath(), Eq("prop4.prop3")); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema3_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema3_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for Schema2: + // {"prop1.prop1", "prop1.prop2", + // "prop1.prop3", "prop2", "prop3"} + // + // Indexable properties: + // {"prop1.prop2", "prop1.prop3", "prop2", "prop3"} + // + // Indexable_nested_properties set for Schema3.prop1 does not propagate + // to Schema2. + SchemaPropertyIterator schema2_iterator(schema_type_config2, type_config_map); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("prop1.prop1")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(0))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("prop1.prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("prop1.prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config1.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("prop2")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(1))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), IsOk()); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyPath(), Eq("prop3")); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config2.properties(2))); + EXPECT_THAT(schema2_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema2_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} TEST(SchemaPropertyIteratorTest, SingleLevelCycle) { std::string schema_a = "A"; std::string schema_b = "B"; @@ -457,13 +1375,13 @@ TEST(SchemaPropertyIteratorTest, SingleLevelCycle) { Eq("schemaAprop1.schemaBprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -477,7 +1395,7 @@ TEST(SchemaPropertyIteratorTest, SingleLevelCycle) { EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_b_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -542,20 +1460,20 @@ TEST(SchemaPropertyIteratorTest, MultipleLevelCycle) { Eq("schemaAprop1.schemaBprop1.schemaCprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop1.schemaBprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -573,20 +1491,20 @@ TEST(SchemaPropertyIteratorTest, MultipleLevelCycle) { Eq("schemaBprop1.schemaCprop1.schemaAprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop1.schemaCprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_b_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -604,32 +1522,226 @@ TEST(SchemaPropertyIteratorTest, MultipleLevelCycle) { Eq("schemaCprop1.schemaAprop1.schemaBprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop1.schemaAprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_c_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); } +TEST(SchemaPropertyIteratorTest, SingleLevelCycleWithIndexableList) { + std::string schema_a = "A"; + std::string schema_b = "B"; + + // Create schema with A -> B -> B -> B... + SchemaTypeConfigProto schema_type_config_a = + SchemaTypeConfigBuilder() + .SetType(schema_a) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaAprop1") + .SetDataTypeDocument( + schema_b, /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_b = + SchemaTypeConfigBuilder() + .SetType(schema_b) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaBprop2") + .SetDataTypeDocument( + schema_b, /*indexable_nested_properties_list=*/ + {"schemaBprop1", "schemaBprop2.schemaBprop1", + "schemaBprop2.schemaBprop3", + "schemaBprop2.schemaBprop2.schemaBprop3"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop3") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + + SchemaUtil::TypeConfigMap type_config_map = { + {schema_a, schema_type_config_a}, {schema_b, schema_type_config_b}}; + + // Order of iteration and whether each property is indexable for schema A: + // {"schemaAprop1.schemaBprop1" (true), + // "schemaAprop1.schemaBprop2.schemaBprop1" (true), + // "schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop1" (true), + // "schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop1" (false), + // "schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop3" (true), + // "schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop3" (true), + // "schemaAprop1.schemaBprop2.schemaBprop3" (false), + // "schemaAprop1.schemaBprop3" (true), + // "schemaAprop2" (true)} + SchemaPropertyIterator schema_a_iterator(schema_type_config_a, + type_config_map); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2.schemaBprop1")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop1")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop1")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2.schemaBprop2.schemaBprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2.schemaBprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for schema B: + // {"schemaBprop1" (true), + // "schemaBprop2.schemaBprop1" (true), + // "schemaBprop2.schemaBprop2.schemaBprop1" (true), + // "schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop1" (false), + // "schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop3" (true), + // "schemaBprop2.schemaBprop2.schemaBprop3" (true), + // "schemaBprop2.schemaBprop3" (false), + // "schemaBprop3" (true)} + SchemaPropertyIterator schema_b_iterator(schema_type_config_b, + type_config_map); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop1")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop2.schemaBprop1")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop2.schemaBprop2.schemaBprop1")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop1")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop2.schemaBprop2.schemaBprop2.schemaBprop3")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop2.schemaBprop2.schemaBprop3")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop2.schemaBprop3")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop3")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + TEST(SchemaPropertyIteratorTest, MultipleCycles) { std::string schema_a = "A"; std::string schema_b = "B"; std::string schema_c = "C"; std::string schema_d = "D"; - // Create schema with D <-> A -> B -> C -> A -> B -> C -> A... + // Create the following schema: + // D <--> A <--- C + // \ ^ + // v / + // B // Schema type A has two cycles: A-B-C-A and A-D-A SchemaTypeConfigProto schema_type_config_a = SchemaTypeConfigBuilder() @@ -701,27 +1813,27 @@ TEST(SchemaPropertyIteratorTest, MultipleCycles) { Eq("schemaAprop1.schemaBprop1.schemaCprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop1.schemaBprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop3.schemaDprop2")); EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_d.properties(1))); - EXPECT_THAT(schema_a_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_a_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -740,27 +1852,27 @@ TEST(SchemaPropertyIteratorTest, MultipleCycles) { Eq("schemaBprop1.schemaCprop1.schemaAprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_d.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop1.schemaCprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop2")); EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_b_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_b_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -778,27 +1890,27 @@ TEST(SchemaPropertyIteratorTest, MultipleCycles) { Eq("schemaCprop1.schemaAprop1.schemaBprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop1.schemaAprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop1.schemaAprop3.schemaDprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_d.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop2")); EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_c_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_c_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); @@ -817,32 +1929,1336 @@ TEST(SchemaPropertyIteratorTest, MultipleCycles) { Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_c.properties(1))); - EXPECT_THAT(schema_d_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), Eq("schemaDprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, MultipleCyclesWithIndexableList) { + std::string schema_a = "A"; + std::string schema_b = "B"; + std::string schema_c = "C"; + std::string schema_d = "D"; + + // Create the following schema: + // D <--> A <--- C + // \ ^ + // v / + // B + // Schema type A has two cycles: A-B-C-A and A-D-A + SchemaTypeConfigProto schema_type_config_a = + SchemaTypeConfigBuilder() + .SetType(schema_a) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop1") + .SetDataTypeDocument( + schema_b, /*indexable_nested_properties_list=*/ + {"schemaBprop2", "schemaBprop1.schemaCprop1.schemaAprop2", + "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2", + "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2", + "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1." + "schemaAprop2"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop3") + .SetDataTypeDocument( + schema_d, /*indexable_nested_properties_list=*/ + {"schemaDprop2", "schemaDprop1.schemaAprop2", + "schemaDprop1.schemaAprop1.schemaBprop2", + "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2", + "schemaDprop1.schemaAprop3.schemaDprop2"})) + .Build(); + SchemaTypeConfigProto schema_type_config_b = + SchemaTypeConfigBuilder() + .SetType(schema_b) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaBprop1") + .SetDataTypeDocument( + schema_c, /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_c = + SchemaTypeConfigBuilder() + .SetType(schema_c) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaCprop1") + .SetDataTypeDocument( + schema_a, /*index_nested_properties=*/false)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaCprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_d = + SchemaTypeConfigBuilder() + .SetType(schema_d) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaDprop1") + .SetDataTypeDocument( + schema_a, /*index_nested_properties=*/false)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaDprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + + SchemaUtil::TypeConfigMap type_config_map = { + {schema_a, schema_type_config_a}, + {schema_b, schema_type_config_b}, + {schema_c, schema_type_config_c}, + {schema_d, schema_type_config_d}}; + + // Order of iteration and whether each property is indexable for schema A: + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" (true), + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2" (true), + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" + // (true), "schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaAprop1.schemaBprop2" (true), + // "schemaAprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2" (true), + // "schemaAprop3.schemaDprop2" (true) + SchemaPropertyIterator schema_a_iterator(schema_type_config_a, + type_config_map); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3." + "schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration and whether each property is indexable for schema B: + // "schemaBprop1.schemaCprop1.schemaAprop2" (false), + // "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" (false), + // "schemaBprop1.schemaCprop2" (true), + // "schemaBprop2" (true) + SchemaPropertyIterator schema_b_iterator(schema_type_config_b, + type_config_map); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for schema C: + // "schemaCprop1.schemaAprop1.schemaBprop2" (false), + // "schemaCprop1.schemaAprop2" (false), + // "schemaCprop1.schemaAprop3.schemaDprop2" (false), + // "schemaCprop2" (true) + SchemaPropertyIterator schema_c_iterator(schema_type_config_c, + type_config_map); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for schema D: + // "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaDprop1.schemaAprop1.schemaBprop2" (false), + // "schemaDprop1.schemaAprop2" (false), + // "schemaDprop2" (true) + SchemaPropertyIterator schema_d_iterator(schema_type_config_d, + type_config_map); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), Eq("schemaDprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, MultipleCyclesWithIndexableList_allIndexTrue) { + std::string schema_a = "A"; + std::string schema_b = "B"; + std::string schema_c = "C"; + std::string schema_d = "D"; + + // Create the following schema: + // D <--> A <--- C + // \ ^ + // v / + // B + // Schema type A has two cycles: A-B-C-A and A-D-A + SchemaTypeConfigProto schema_type_config_a = + SchemaTypeConfigBuilder() + .SetType(schema_a) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop1") + .SetDataTypeDocument( + schema_b, /*indexable_nested_properties_list=*/ + {"schemaBprop2", "schemaBprop1.schemaCprop1.schemaAprop2", + "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2", + "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2", + "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1." + "schemaAprop2"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop3") + .SetDataTypeDocument( + schema_d, /*indexable_nested_properties_list=*/ + {"schemaDprop2", "schemaDprop1.schemaAprop2", + "schemaDprop1.schemaAprop1.schemaBprop2", + "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2", + "schemaDprop1.schemaAprop3.schemaDprop2"})) + .Build(); + SchemaTypeConfigProto schema_type_config_b = + SchemaTypeConfigBuilder() + .SetType(schema_b) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaBprop1") + .SetDataTypeDocument( + schema_c, /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_c = + SchemaTypeConfigBuilder() + .SetType(schema_c) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaCprop1") + .SetDataTypeDocument( + schema_a, /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaCprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_d = + SchemaTypeConfigBuilder() + .SetType(schema_d) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaDprop1") + .SetDataTypeDocument( + schema_a, /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaDprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + + SchemaUtil::TypeConfigMap type_config_map = { + {schema_a, schema_type_config_a}, + {schema_b, schema_type_config_b}, + {schema_c, schema_type_config_c}, + {schema_d, schema_type_config_d}}; + + // Order of iteration and whether each property is indexable for schema A: + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" (true), + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2" (true), + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" + // (true), "schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaAprop1.schemaBprop2" (true), + // "schemaAprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2" (true), + // "schemaAprop3.schemaDprop2" (true) + SchemaPropertyIterator schema_a_iterator(schema_type_config_a, + type_config_map); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3." + "schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration and whether each property is indexable for schema B: + // "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" + // (true), + // "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2" + // (true), + // "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), + // "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" + // (true), "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop2" + // (false), "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" (true), + // "schemaBprop1.schemaCprop1.schemaAprop2" (true), + // "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" + // (true), + // "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2" + // (true), "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), + // "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2" + // (true), "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" (true) + // "schemaBprop1.schemaCprop2" (true) + // "schemaBprop2" (true) + + SchemaPropertyIterator schema_b_iterator(schema_type_config_b, + type_config_map); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1." + "schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1." + "schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1." + "schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1." + "schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1." + "schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1." + "schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1." + "schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration and whether each property is indexable for schema C: + // "schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" + // (true), "schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2" + // (true), + // "schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), + // "schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" + // (true), + // "schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaCprop1.schemaAprop1.schemaBprop2" (true), + // "schemaCprop1.schemaAprop2" (true), + // "schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" + // (true), + // "schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2" (true), + // "schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" (true), + // "schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2" (true), + // "schemaCprop1.schemaAprop3.schemaDprop2" (true) + // "schemaCprop2" (true) + SchemaPropertyIterator schema_c_iterator(schema_type_config_c, + type_config_map); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1." + "schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1." + "schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop1." + "schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop1." + "schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration and whether each property is indexable for schema D: + // "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" + // (true), "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2" + // (true), + // "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), + // "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" + // (true), "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaDprop1.schemaAprop1.schemaBprop2" (true), + // "schemaDprop1.schemaAprop2" (true), + // "schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" + // (true), "schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2" + // (true), "schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop2" (true), + // "schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2" (true), + // "schemaDprop1.schemaAprop3.schemaDprop2" (true), + // "schemaDprop2" (true) + SchemaPropertyIterator schema_d_iterator(schema_type_config_d, + type_config_map); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1." + "schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1." + "schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop1." + "schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop1." + "schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), Eq("schemaDprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_d_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + +TEST(SchemaPropertyIteratorTest, + MultipleCyclesWithIndexableList_nonExistentPropPaths) { + std::string schema_a = "A"; + std::string schema_b = "B"; + std::string schema_c = "C"; + std::string schema_d = "D"; + + // Create the following schema: + // D <--> A <--- C + // \ ^ + // v / + // B + // Schema type A has two cycles: A-B-C-A and A-D-A + SchemaTypeConfigProto schema_type_config_a = + SchemaTypeConfigBuilder() + .SetType(schema_a) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop1") + .SetDataTypeDocument( + schema_b, /*indexable_nested_properties_list=*/ + {"schemaBprop2", "schemaBprop1.schemaCprop1.schemaAprop2", + "schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2", + "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2", + "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1." + "schemaAprop2", + "schemaBprop1.schemaCprop1", + "schemaBprop1.schemaCprop1.schemaAprop3", "schemaAprop2", + "schemaBprop2.schemaCprop2", "schemaBprop1.foo.bar", + "foo", "foo", "bar"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop3") + .SetDataTypeDocument( + schema_d, /*indexable_nested_properties_list=*/ + {"schemaDprop2", "schemaDprop1.schemaAprop2", + "schemaDprop1.schemaAprop1.schemaBprop2", + "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2", + "schemaDprop1.schemaAprop3.schemaDprop2", "schemaBprop2", + "bar", "schemaDprop2.foo", "schemaDprop1", + "schemaAprop3.schemaDprop2"})) + .Build(); + SchemaTypeConfigProto schema_type_config_b = + SchemaTypeConfigBuilder() + .SetType(schema_b) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaBprop1") + .SetDataTypeDocument( + schema_c, /*index_nested_properties=*/true)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_c = + SchemaTypeConfigBuilder() + .SetType(schema_c) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaCprop1") + .SetDataTypeDocument( + schema_a, /*index_nested_properties=*/false)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaCprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_d = + SchemaTypeConfigBuilder() + .SetType(schema_d) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaDprop1") + .SetDataTypeDocument( + schema_a, /*index_nested_properties=*/false)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaDprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + + SchemaUtil::TypeConfigMap type_config_map = { + {schema_a, schema_type_config_a}, + {schema_b, schema_type_config_b}, + {schema_c, schema_type_config_c}, + {schema_d, schema_type_config_d}}; + + // Order of iteration and whether each property is indexable for schema A: + // {"schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2" (true), + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2" (true), + // "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop1.schemaAprop2" + // (true), "schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" + // (true), "schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaAprop1.schemaBprop2" (true), + // "schemaAprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop2" (true), + // "schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2" (true), + // "schemaAprop3.schemaDprop2" (true) + // + // The following properties listed in the indexable_list are not defined + // in the schema and should not be seen during iteration: + // - From schemaAprop1's list: + // "schemaBprop1.schemaCprop1", "schemaBprop1.schemaCprop1.schemaAprop3", + // "schemaAprop2", "schemaBprop2.schemaCprop2", "schemaBprop1.foo.bar", + // "foo", "bar" + // - From schemaAprop3's list: + // "schemaBprop2", "bar", "schemaDprop2.foo", "schemaDprop1", + // "schemaAprop3.schemaDprop2" + SchemaPropertyIterator schema_a_iterator(schema_type_config_a, + type_config_map); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3." + "schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT( + schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration and whether each property is indexable for schema B: + // "schemaBprop1.schemaCprop1.schemaAprop2" (false), + // "schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2" (false), + // "schemaBprop1.schemaCprop2" (true), + // "schemaBprop2" (true) + SchemaPropertyIterator schema_b_iterator(schema_type_config_b, + type_config_map); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), + Eq("schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyPath(), Eq("schemaBprop2")); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_b_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_b_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for schema C: + // "schemaCprop1.schemaAprop1.schemaBprop2" (false), + // "schemaCprop1.schemaAprop2" (false), + // "schemaCprop1.schemaAprop3.schemaDprop2" (false), + // "schemaCprop2" (true) + SchemaPropertyIterator schema_c_iterator(schema_type_config_c, + type_config_map); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), + Eq("schemaCprop1.schemaAprop3.schemaDprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_d.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_c_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyPath(), Eq("schemaCprop2")); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_c_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_c_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); + + // Order of iteration for schema D: + // "schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2" (false), + // "schemaDprop1.schemaAprop1.schemaBprop2" (false), + // "schemaDprop1.schemaAprop2" (false), + // "schemaDprop2" (true) + SchemaPropertyIterator schema_d_iterator(schema_type_config_d, + type_config_map); + + EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), + Eq("schemaDprop1.schemaAprop1.schemaBprop1.schemaCprop2")); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_c.properties(1))); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), Eq("schemaDprop1.schemaAprop1.schemaBprop2")); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_b.properties(1))); - EXPECT_THAT(schema_d_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), Eq("schemaDprop1.schemaAprop2")); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_a.properties(1))); - EXPECT_THAT(schema_d_iterator.GetCurrentNestedIndexable(), IsFalse()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsFalse()); EXPECT_THAT(schema_d_iterator.Advance(), IsOk()); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyPath(), Eq("schemaDprop2")); EXPECT_THAT(schema_d_iterator.GetCurrentPropertyConfig(), EqualsProto(schema_type_config_d.properties(1))); - EXPECT_THAT(schema_d_iterator.GetCurrentNestedIndexable(), IsTrue()); + EXPECT_THAT(schema_d_iterator.GetCurrentPropertyIndexable(), IsTrue()); EXPECT_THAT(schema_d_iterator.Advance(), StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); } +TEST(SchemaPropertyIteratorTest, TopLevelCycleWithMultipleIndexableLists) { + std::string schema_a = "A"; + std::string schema_b = "B"; + std::string schema_c = "C"; + std::string schema_d = "D"; + + // Create the following schema: + // A <-> A -> B + // A has a top-level property that is a self-reference. + SchemaTypeConfigProto schema_type_config_a = + SchemaTypeConfigBuilder() + .SetType(schema_a) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaAprop1") + .SetDataTypeDocument( + schema_b, /*indexable_nested_properties_list=*/ + {"schemaBprop1", "schemaBprop2"})) + .AddProperty(PropertyConfigBuilder() + .SetName("schemaAprop2") + .SetDataTypeDocument( + schema_a, /*indexable_nested_properties_list=*/ + {"schemaAprop1.schemaBprop2", + "schemaAprop1.schemaBprop3"})) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaAprop3") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + SchemaTypeConfigProto schema_type_config_b = + SchemaTypeConfigBuilder() + .SetType(schema_b) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop1") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop2") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .AddProperty( + PropertyConfigBuilder() + .SetName("schemaBprop3") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)) + .Build(); + + SchemaUtil::TypeConfigMap type_config_map = { + {schema_a, schema_type_config_a}, {schema_b, schema_type_config_b}}; + + // Order of iteration for Schema A: + // "schemaAprop1.schemaBprop1" (true) + // "schemaAprop1.schemaBprop2" (true) + // "schemaAprop1.schemaBprop3" (false) + // "schemaAprop2.schemaAprop1.schemaBprop1" (false) + // "schemaAprop2.schemaAprop1.schemaBprop2" (true) + // "schemaAprop2.schemaAprop1.schemaBprop3" (true) + // "schemaAprop2.schemaAprop3" (false) + // "schemaAprop3" (true) + SchemaPropertyIterator schema_a_iterator(schema_type_config_a, + type_config_map); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop1")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop1.schemaBprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop2.schemaAprop1.schemaBprop1")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(0))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop2.schemaAprop1.schemaBprop2")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(1))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop2.schemaAprop1.schemaBprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_b.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), + Eq("schemaAprop2.schemaAprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsFalse()); + + EXPECT_THAT(schema_a_iterator.Advance(), IsOk()); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyPath(), Eq("schemaAprop3")); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyConfig(), + EqualsProto(schema_type_config_a.properties(2))); + EXPECT_THAT(schema_a_iterator.GetCurrentPropertyIndexable(), IsTrue()); + + EXPECT_THAT(schema_a_iterator.Advance(), + StatusIs(libtextclassifier3::StatusCode::OUT_OF_RANGE)); +} + } // namespace } // namespace lib diff --git a/icing/schema/schema-type-manager.cc b/icing/schema/schema-type-manager.cc index f3a86d4..ca2f45c 100644 --- a/icing/schema/schema-type-manager.cc +++ b/icing/schema/schema-type-manager.cc @@ -55,7 +55,7 @@ SchemaTypeManager::Create(const SchemaUtil::TypeConfigMap& type_config_map, } // Process section (indexable property) - if (iterator.GetCurrentNestedIndexable()) { + if (iterator.GetCurrentPropertyIndexable()) { ICING_RETURN_IF_ERROR( section_manager_builder.ProcessSchemaTypePropertyConfig( schema_type_id, iterator.GetCurrentPropertyConfig(), diff --git a/icing/schema/schema-util.cc b/icing/schema/schema-util.cc index 371ed00..6d63b10 100644 --- a/icing/schema/schema-util.cc +++ b/icing/schema/schema-util.cc @@ -115,6 +115,34 @@ bool IsIntegerNumericMatchTypeCompatible( return old_indexed.numeric_match_type() == new_indexed.numeric_match_type(); } +bool IsDocumentIndexingCompatible(const DocumentIndexingConfig& old_indexed, + const DocumentIndexingConfig& new_indexed) { + // TODO(b/265304217): This could mark the new schema as incompatible and + // generate some unnecessary index rebuilds if the two schemas have an + // equivalent set of indexed properties, but changed the way that it is + // declared. + if (old_indexed.index_nested_properties() != + new_indexed.index_nested_properties()) { + return false; + } + + if (old_indexed.indexable_nested_properties_list().size() != + new_indexed.indexable_nested_properties_list().size()) { + return false; + } + + std::unordered_set<std::string_view> old_indexable_nested_properies_set( + old_indexed.indexable_nested_properties_list().begin(), + old_indexed.indexable_nested_properties_list().end()); + for (const auto& property : new_indexed.indexable_nested_properties_list()) { + if (old_indexable_nested_properies_set.find(property) == + old_indexable_nested_properies_set.end()) { + return false; + } + } + return true; +} + void AddIncompatibleChangeToDelta( std::unordered_set<std::string>& incompatible_delta, const SchemaTypeConfigProto& old_type_config, @@ -571,6 +599,10 @@ libtextclassifier3::StatusOr<SchemaUtil::DependentMap> SchemaUtil::Validate( "data_types in schema property '", schema_type, ".", property_name, "'")); } + + ICING_RETURN_IF_ERROR(ValidateDocumentIndexingConfig( + property_config.document_indexing_config(), schema_type, + property_name)); } ICING_RETURN_IF_ERROR(ValidateCardinality(property_config.cardinality(), @@ -751,6 +783,19 @@ libtextclassifier3::Status SchemaUtil::ValidateJoinableConfig( return libtextclassifier3::Status::OK; } +libtextclassifier3::Status SchemaUtil::ValidateDocumentIndexingConfig( + const DocumentIndexingConfig& config, std::string_view schema_type, + std::string_view property_name) { + if (!config.indexable_nested_properties_list().empty() && + config.index_nested_properties()) { + return absl_ports::InvalidArgumentError(absl_ports::StrCat( + "DocumentIndexingConfig.index_nested_properties is required to be " + "false when providing a non-empty indexable_nested_properties_list " + "for property '", schema_type, ".", property_name, "'")); + } + return libtextclassifier3::Status::OK; +} + /* static */ bool SchemaUtil::IsIndexedProperty( const PropertyConfigProto& property_config) { switch (property_config.data_type()) { @@ -1005,10 +1050,9 @@ const SchemaUtil::SchemaDelta SchemaUtil::ComputeCompatibilityDelta( !IsIntegerNumericMatchTypeCompatible( old_property_config.integer_indexing_config(), new_property_config->integer_indexing_config()) || - old_property_config.document_indexing_config() - .index_nested_properties() != - new_property_config->document_indexing_config() - .index_nested_properties()) { + !IsDocumentIndexingCompatible( + old_property_config.document_indexing_config(), + new_property_config->document_indexing_config())) { is_index_incompatible = true; } diff --git a/icing/schema/schema-util.h b/icing/schema/schema-util.h index e707758..0adc0ae 100644 --- a/icing/schema/schema-util.h +++ b/icing/schema/schema-util.h @@ -157,6 +157,9 @@ class SchemaUtil { // (property whose joinable config is not NONE), OR // ii. Any type node in the cycle has a nested-type (direct or // indirect) with a joinable property. + // 15. For DOCUMENT data types, if + // DocumentIndexingConfig.indexable_nested_properties_list is non-empty, + // DocumentIndexingConfig.index_nested_properties must be false. // // Returns: // On success, a dependent map from each types to their dependent types @@ -315,6 +318,17 @@ class SchemaUtil { PropertyConfigProto::Cardinality::Code cardinality, std::string_view schema_type, std::string_view property_name); + // Checks that the 'document_indexing_config' satisfies the following rule: + // 1. If indexable_nested_properties is non-empty, index_nested_properties + // must be set to false. + // + // Returns: + // INVALID_ARGUMENT if any of the rules are not followed + // OK on success + static libtextclassifier3::Status ValidateDocumentIndexingConfig( + const DocumentIndexingConfig& config, std::string_view schema_type, + std::string_view property_name); + // Returns if 'parent_type' is a direct or indirect parent of 'child_type'. static bool IsParent(const SchemaUtil::InheritanceMap& inheritance_map, std::string_view parent_type, diff --git a/icing/schema/schema-util_test.cc b/icing/schema/schema-util_test.cc index 40e30b0..6f9e420 100644 --- a/icing/schema/schema-util_test.cc +++ b/icing/schema/schema-util_test.cc @@ -14,6 +14,8 @@ #include "icing/schema/schema-util.h" +#include <initializer_list> +#include <string> #include <string_view> #include <unordered_set> @@ -3081,6 +3083,239 @@ TEST_P(SchemaUtilTest, IndexNestedDocumentsIndexIncompatible) { EXPECT_THAT(actual, Eq(schema_delta)); } +TEST_P(SchemaUtilTest, AddOrDropIndexableNestedProperties_IndexIncompatible) { + SchemaTypeConfigProto email_type_config = + SchemaTypeConfigBuilder() + .SetType(kEmailType) + .AddProperty(PropertyConfigBuilder() + .SetName("recipient") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .Build(); + SchemaProto schema_1 = + SchemaBuilder() + .AddType(email_type_config) + .AddType( + SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties_list=*/ + {"recipient", "subject", "body"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaProto schema_2 = + SchemaBuilder() + .AddType(email_type_config) + .AddType(SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties=*/ + {"recipient", "subject"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + // Dropping some indexable_nested_properties should make kPersonType + // index_incompatible. kEmailType should be unaffected. + SchemaUtil::SchemaDelta schema_delta; + schema_delta.schema_types_index_incompatible.emplace(kPersonType); + SchemaUtil::DependentMap dependents_map = {{kEmailType, {{kPersonType, {}}}}}; + SchemaUtil::SchemaDelta actual = + SchemaUtil::ComputeCompatibilityDelta(schema_1, schema_2, dependents_map); + EXPECT_THAT(actual, Eq(schema_delta)); + + // Adding some indexable_nested_properties should also make kPersonType + // index_incompatible. kEmailType should be unaffected. + actual = + SchemaUtil::ComputeCompatibilityDelta(schema_2, schema_1, dependents_map); + EXPECT_THAT(actual, Eq(schema_delta)); +} + +TEST_P(SchemaUtilTest, ChangingIndexableNestedProperties_IndexIncompatible) { + SchemaTypeConfigProto email_type_config = + SchemaTypeConfigBuilder() + .SetType(kEmailType) + .AddProperty(PropertyConfigBuilder() + .SetName("recipient") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .Build(); + SchemaProto schema_1 = + SchemaBuilder() + .AddType(email_type_config) + .AddType( + SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties_list=*/ + {"recipient", "subject"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaProto schema_2 = + SchemaBuilder() + .AddType(email_type_config) + .AddType( + SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties_list=*/ + {"recipient", "body"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + // Changing 'subject' to 'body' for indexable_nested_properties_list should + // make kPersonType index_incompatible. kEmailType should be unaffected. + SchemaUtil::SchemaDelta schema_delta; + schema_delta.schema_types_index_incompatible.emplace(kPersonType); + SchemaUtil::DependentMap dependents_map = {{kEmailType, {{kPersonType, {}}}}}; + SchemaUtil::SchemaDelta actual = + SchemaUtil::ComputeCompatibilityDelta(schema_1, schema_2, dependents_map); + EXPECT_THAT(actual, Eq(schema_delta)); +} + +TEST_P(SchemaUtilTest, IndexableNestedPropertiesFullSet_IndexIncompatible) { + SchemaTypeConfigProto email_type_config = + SchemaTypeConfigBuilder() + .SetType(kEmailType) + .AddProperty(PropertyConfigBuilder() + .SetName("recipient") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .Build(); + SchemaProto schema_1 = + SchemaBuilder() + .AddType(email_type_config) + .AddType(SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaProto schema_2 = + SchemaBuilder() + .AddType(email_type_config) + .AddType( + SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties_list=*/ + {"recipient", "body", "subject"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + // This scenario also invalidates kPersonType and triggers an index rebuild at + // the moment, even though the set of indexable_nested_properties from + // schema_1 to schema_2 should be the same. + SchemaUtil::SchemaDelta schema_delta; + schema_delta.schema_types_index_incompatible.emplace(kPersonType); + SchemaUtil::DependentMap dependents_map = {{kEmailType, {{kPersonType, {}}}}}; + SchemaUtil::SchemaDelta actual = + SchemaUtil::ComputeCompatibilityDelta(schema_1, schema_2, dependents_map); + EXPECT_THAT(actual, Eq(schema_delta)); +} + +TEST_P(SchemaUtilTest, + ChangingIndexableNestedPropertiesOrder_IndexIsCompatible) { + SchemaTypeConfigProto email_type_config = + SchemaTypeConfigBuilder() + .SetType(kEmailType) + .AddProperty(PropertyConfigBuilder() + .SetName("recipient") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .Build(); + SchemaProto schema_1 = + SchemaBuilder() + .AddType(email_type_config) + .AddType( + SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties_list=*/ + {"recipient", "subject", "body"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaProto schema_2 = + SchemaBuilder() + .AddType(email_type_config) + .AddType( + SchemaTypeConfigBuilder() + .SetType(kPersonType) + .AddProperty(PropertyConfigBuilder() + .SetName("emails") + .SetDataTypeDocument( + kEmailType, + /*indexable_nested_properties_list=*/ + {"subject", "body", "recipient"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + // Changing order of elements in indexable_nested_properties_list should have + // no effect on schema compatibility. + SchemaUtil::SchemaDelta schema_delta; + SchemaUtil::DependentMap dependents_map = {{kEmailType, {{kPersonType, {}}}}}; + SchemaUtil::SchemaDelta actual = + SchemaUtil::ComputeCompatibilityDelta(schema_1, schema_2, dependents_map); + EXPECT_THAT(actual, Eq(schema_delta)); + EXPECT_THAT(actual.schema_types_index_incompatible, IsEmpty()); +} + TEST_P(SchemaUtilTest, ValidateStringIndexingConfigShouldHaveTermMatchType) { SchemaProto schema = SchemaBuilder() @@ -3673,6 +3908,137 @@ TEST_P(SchemaUtilTest, ValidateNestedJoinablePropertyDiamondRelationship) { StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT)); } +TEST_P(SchemaUtilTest, + ValidDocumentIndexingConfigFields_emptyIndexableListBooleanTrue) { + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder() + .SetType("InnerSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("prop1") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("prop2") + .SetDataTypeString(TERM_MATCH_UNKNOWN, + TOKENIZER_NONE) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("OuterSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("InnerProperty") + .SetDataTypeDocument( + "InnerSchema", + /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaTypeConfigProto* outerSchemaType = schema.mutable_types(1); + outerSchemaType->mutable_properties(0) + ->mutable_document_indexing_config() + ->clear_indexable_nested_properties_list(); + + EXPECT_THAT(SchemaUtil::Validate(schema, GetParam()), IsOk()); +} + +TEST_P(SchemaUtilTest, + ValidDocumentIndexingConfigFields_emptyIndexableListBooleanFalse) { + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder() + .SetType("InnerSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("prop1") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("prop2") + .SetDataTypeString(TERM_MATCH_UNKNOWN, + TOKENIZER_NONE) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("OuterSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("InnerProperty") + .SetDataTypeDocument( + "InnerSchema", + /*index_nested_properties=*/false) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaTypeConfigProto* outerSchemaType = schema.mutable_types(1); + outerSchemaType->mutable_properties(0) + ->mutable_document_indexing_config() + ->clear_indexable_nested_properties_list(); + + EXPECT_THAT(SchemaUtil::Validate(schema, GetParam()), IsOk()); +} + +TEST_P(SchemaUtilTest, + ValidDocumentIndexingConfigFields_nonEmptyIndexableList) { + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder() + .SetType("InnerSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("prop1") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("OuterSchema") + .AddProperty( + PropertyConfigBuilder() + .SetName("InnerProperty") + .SetDataTypeDocument( + "InnerSchema", + /*indexable_nested_properties_list=*/ + std::initializer_list<std::string>{"prop1"}) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + SchemaTypeConfigProto* outerSchemaType = schema.mutable_types(1); + outerSchemaType->mutable_properties(0) + ->mutable_document_indexing_config() + ->set_index_nested_properties(false); + EXPECT_THAT(SchemaUtil::Validate(schema, GetParam()), IsOk()); +} + +TEST_P(SchemaUtilTest, InvalidDocumentIndexingConfigFields) { + // If indexable_nested_properties is non-empty, index_nested_properties is + // required to be false. + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder() + .SetType("InnerSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("prop1") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("OuterSchema") + .AddProperty(PropertyConfigBuilder() + .SetName("InnerProperty") + .SetDataTypeDocument( + "InnerSchema", + /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_REPEATED))) + .Build(); + + // Setting a non-empty indexable_nested_properties_list while + // index_nested_properties=true is invalid. + SchemaTypeConfigProto* outerSchemaType = schema.mutable_types(1); + outerSchemaType->mutable_properties(0) + ->mutable_document_indexing_config() + ->add_indexable_nested_properties_list("prop"); + + EXPECT_THAT(SchemaUtil::Validate(schema, GetParam()), + StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT)); +} + TEST_P(SchemaUtilTest, MultipleReferencesToSameNestedSchemaOk) { SchemaProto schema = SchemaBuilder() diff --git a/proto/icing/proto/schema.proto b/proto/icing/proto/schema.proto index b972ece..c716dba 100644 --- a/proto/icing/proto/schema.proto +++ b/proto/icing/proto/schema.proto @@ -138,15 +138,22 @@ message StringIndexingConfig { } // Describes how a document property should be indexed. -// Next tag: 2 +// Next tag: 3 message DocumentIndexingConfig { // OPTIONAL: Whether nested properties within the document property should be - // indexed. If true, then the nested properties will be indexed according to + // indexed. If true, then all nested properties will be indexed according to // the property's own indexing configurations. If false, nested documents' // properties will not be indexed even if they have an indexing configuration. // // The default value is false. optional bool index_nested_properties = 1; + + // List of nested properties within the document to index. Only the + // provided list of properties will be indexed according to the property's + // indexing configurations. + // + // index_nested_properties must be false in order to use this feature. + repeated string indexable_nested_properties_list = 2; } // Describes how a int64 property should be indexed. diff --git a/proto/icing/proto/search.proto b/proto/icing/proto/search.proto index fca669a..411ad3e 100644 --- a/proto/icing/proto/search.proto +++ b/proto/icing/proto/search.proto @@ -106,7 +106,7 @@ message SearchSpecProto { // Client-supplied specifications on what to include/how to format the search // results. -// Next tag: 9 +// Next tag: 10 message ResultSpecProto { // The results will be returned in pages, and num_per_page specifies the // number of documents in one page. @@ -211,6 +211,18 @@ message ResultSpecProto { // The max # of child documents will be attached and returned in the result // for each parent. It is only used for join API. optional int32 max_joined_children_per_parent_to_return = 8; + + // The max # of results being scored and ranked. + // Running time of ScoringProcessor and Ranker is O(num_to_score) according to + // results of //icing/scoring:score-and-rank_benchmark. Note that + // the process includes scoring, building a heap, and popping results from the + // heap. + // + // 30000 results can be scored and ranked within 3 ms on a Pixel 3 XL + // according to results of + // //icing/scoring:score-and-rank_benchmark, so set it as the + // default value. + optional int32 num_to_score = 9 [default = 30000]; } // The representation of a single match within a DocumentProto property. diff --git a/synced_AOSP_CL_number.txt b/synced_AOSP_CL_number.txt index afb1234..20e7738 100644 --- a/synced_AOSP_CL_number.txt +++ b/synced_AOSP_CL_number.txt @@ -1 +1 @@ -set(synced_AOSP_CL_number=537223436) +set(synced_AOSP_CL_number=544124118) |