// Copyright (C) 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include "icing/text_classifier/lib3/utils/base/status.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "icing/document-builder.h" #include "icing/file/filesystem.h" #include "icing/file/mock-filesystem.h" #include "icing/icing-search-engine.h" #include "icing/jni/jni-cache.h" #include "icing/join/join-processor.h" #include "icing/portable/endian.h" #include "icing/portable/equals-proto.h" #include "icing/portable/platform.h" #include "icing/proto/debug.pb.h" #include "icing/proto/document.pb.h" #include "icing/proto/document_wrapper.pb.h" #include "icing/proto/initialize.pb.h" #include "icing/proto/logging.pb.h" #include "icing/proto/optimize.pb.h" #include "icing/proto/persist.pb.h" #include "icing/proto/reset.pb.h" #include "icing/proto/schema.pb.h" #include "icing/proto/scoring.pb.h" #include "icing/proto/search.pb.h" #include "icing/proto/status.pb.h" #include "icing/proto/storage.pb.h" #include "icing/proto/term.pb.h" #include "icing/proto/usage.pb.h" #include "icing/query/query-features.h" #include "icing/schema-builder.h" #include "icing/store/document-log-creator.h" #include "icing/testing/common-matchers.h" #include "icing/testing/fake-clock.h" #include "icing/testing/icu-data-file-helper.h" #include "icing/testing/jni-test-helpers.h" #include "icing/testing/test-data.h" #include "icing/testing/tmp-directory.h" namespace icing { namespace lib { namespace { using ::icing::lib::portable_equals_proto::EqualsProto; using ::testing::Eq; using ::testing::Ge; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::Lt; using ::testing::Return; // For mocking purpose, we allow tests to provide a custom Filesystem. class TestIcingSearchEngine : public IcingSearchEngine { public: TestIcingSearchEngine(const IcingSearchEngineOptions& options, std::unique_ptr filesystem, std::unique_ptr icing_filesystem, std::unique_ptr clock, std::unique_ptr jni_cache) : IcingSearchEngine(options, std::move(filesystem), std::move(icing_filesystem), std::move(clock), std::move(jni_cache)) {} }; std::string GetTestBaseDir() { return GetTestTempDir() + "/icing"; } // This test is meant to cover all tests relating to // IcingSearchEngine::Optimize. class IcingSearchEngineOptimizeTest : public testing::Test { protected: void SetUp() override { if (!IsCfStringTokenization() && !IsReverseJniTokenization()) { // If we've specified using the reverse-JNI method for segmentation (i.e. // not ICU), then we won't have the ICU data file included to set up. // Technically, we could choose to use reverse-JNI for segmentation AND // include an ICU data file, but that seems unlikely and our current BUILD // setup doesn't do this. // File generated via icu_data_file rule in //icing/BUILD. std::string icu_data_file_path = GetTestFilePath("icing/icu.dat"); ICING_ASSERT_OK( icu_data_file_helper::SetUpICUDataFile(icu_data_file_path)); } filesystem_.CreateDirectoryRecursively(GetTestBaseDir().c_str()); } void TearDown() override { filesystem_.DeleteDirectoryRecursively(GetTestBaseDir().c_str()); } const Filesystem* filesystem() const { return &filesystem_; } private: Filesystem filesystem_; }; // Non-zero value so we don't override it to be the current time constexpr int64_t kDefaultCreationTimestampMs = 1575492852000; IcingSearchEngineOptions GetDefaultIcingOptions() { IcingSearchEngineOptions icing_options; icing_options.set_base_dir(GetTestBaseDir()); return icing_options; } ScoringSpecProto GetDefaultScoringSpec() { ScoringSpecProto scoring_spec; scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE); return scoring_spec; } // TODO(b/272145329): create SearchSpecBuilder, JoinSpecBuilder, // SearchResultProtoBuilder and ResultProtoBuilder for unit tests and build all // instances by them. TEST_F(IcingSearchEngineOptimizeTest, AllPageTokensShouldBeInvalidatedAfterOptimization) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); SearchSpecProto search_spec; search_spec.set_term_match_type(TermMatchType::PREFIX); search_spec.set_query("message"); ResultSpecProto result_spec; result_spec.set_num_per_page(1); // Searches and gets the first page, 1 result SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); *expected_search_result_proto.mutable_results()->Add()->mutable_document() = document2; SearchResultProto search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); EXPECT_THAT(search_result_proto.next_page_token(), Gt(kInvalidNextPageToken)); uint64_t next_page_token = search_result_proto.next_page_token(); // Since the token is a random number, we don't need to verify expected_search_result_proto.set_next_page_token(next_page_token); EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Now document1 is still to be fetched. OptimizeResultProto optimize_result_proto; optimize_result_proto.mutable_status()->set_code(StatusProto::OK); optimize_result_proto.mutable_status()->set_message(""); OptimizeResultProto actual_result = icing.Optimize(); actual_result.clear_optimize_stats(); ASSERT_THAT(actual_result, EqualsProto(optimize_result_proto)); // Tries to fetch the second page, no results since all tokens have been // invalidated during Optimize() expected_search_result_proto.clear_results(); expected_search_result_proto.clear_next_page_token(); search_result_proto = icing.GetNextPage(next_page_token); EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizationShouldRemoveDeletedDocs) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); IcingSearchEngineOptions icing_options = GetDefaultIcingOptions(); GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND); expected_get_result_proto.mutable_status()->set_message( "Document (namespace, uri1) not found."); { IcingSearchEngine icing(icing_options, GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); // Deletes document1 ASSERT_THAT(icing.Delete("namespace", "uri1").status(), ProtoIsOk()); const std::string document_log_path = icing_options.base_dir() + "/document_dir/" + DocumentLogCreator::GetDocumentLogFilename(); int64_t document_log_size_before = filesystem()->GetFileSize(document_log_path.c_str()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); int64_t document_log_size_after = filesystem()->GetFileSize(document_log_path.c_str()); // Validates that document can't be found right after Optimize() EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); // Validates that document is actually removed from document log EXPECT_THAT(document_log_size_after, Lt(document_log_size_before)); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(icing_options, GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizationShouldDeleteTemporaryDirectory) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); IcingSearchEngineOptions icing_options = GetDefaultIcingOptions(); IcingSearchEngine icing(icing_options, GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); // Create a tmp dir that will be used in Optimize() to swap files, // this validates that any tmp dirs will be deleted before using. const std::string tmp_dir = icing_options.base_dir() + "/document_dir_optimize_tmp"; const std::string tmp_file = tmp_dir + "/file"; ASSERT_TRUE(filesystem()->CreateDirectory(tmp_dir.c_str())); ScopedFd fd(filesystem()->OpenForWrite(tmp_file.c_str())); ASSERT_TRUE(fd.is_valid()); ASSERT_TRUE(filesystem()->Write(fd.get(), "1234", 4)); fd.reset(); EXPECT_THAT(icing.Optimize().status(), ProtoIsOk()); EXPECT_FALSE(filesystem()->DirectoryExists(tmp_dir.c_str())); EXPECT_FALSE(filesystem()->FileExists(tmp_file.c_str())); } TEST_F(IcingSearchEngineOptimizeTest, GetOptimizeInfoHasCorrectStats) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .SetCreationTimestampMs(100) .SetTtlMs(500) .Build(); { auto fake_clock = std::make_unique(); fake_clock->SetSystemTimeMilliseconds(1000); TestIcingSearchEngine icing(GetDefaultIcingOptions(), std::make_unique(), std::make_unique(), std::move(fake_clock), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); // Just initialized, nothing is optimizable yet. GetOptimizeInfoResultProto optimize_info = icing.GetOptimizeInfo(); EXPECT_THAT(optimize_info.status(), ProtoIsOk()); EXPECT_THAT(optimize_info.optimizable_docs(), Eq(0)); EXPECT_THAT(optimize_info.estimated_optimizable_bytes(), Eq(0)); EXPECT_THAT(optimize_info.time_since_last_optimize_ms(), Eq(0)); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); // Only have active documents, nothing is optimizable yet. optimize_info = icing.GetOptimizeInfo(); EXPECT_THAT(optimize_info.status(), ProtoIsOk()); EXPECT_THAT(optimize_info.optimizable_docs(), Eq(0)); EXPECT_THAT(optimize_info.estimated_optimizable_bytes(), Eq(0)); EXPECT_THAT(optimize_info.time_since_last_optimize_ms(), Eq(0)); // Deletes document1 ASSERT_THAT(icing.Delete("namespace", "uri1").status(), ProtoIsOk()); optimize_info = icing.GetOptimizeInfo(); EXPECT_THAT(optimize_info.status(), ProtoIsOk()); EXPECT_THAT(optimize_info.optimizable_docs(), Eq(1)); EXPECT_THAT(optimize_info.estimated_optimizable_bytes(), Gt(0)); EXPECT_THAT(optimize_info.time_since_last_optimize_ms(), Eq(0)); int64_t first_estimated_optimizable_bytes = optimize_info.estimated_optimizable_bytes(); // Add a second document, but it'll be expired since the time (1000) is // greater than the document's creation timestamp (100) + the document's ttl // (500) ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); optimize_info = icing.GetOptimizeInfo(); EXPECT_THAT(optimize_info.status(), ProtoIsOk()); EXPECT_THAT(optimize_info.optimizable_docs(), Eq(2)); EXPECT_THAT(optimize_info.estimated_optimizable_bytes(), Gt(first_estimated_optimizable_bytes)); EXPECT_THAT(optimize_info.time_since_last_optimize_ms(), Eq(0)); // Optimize ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); } { // Recreate with new time auto fake_clock = std::make_unique(); fake_clock->SetSystemTimeMilliseconds(5000); TestIcingSearchEngine icing(GetDefaultIcingOptions(), std::make_unique(), std::make_unique(), std::move(fake_clock), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); // Nothing is optimizable now that everything has been optimized away. GetOptimizeInfoResultProto optimize_info = icing.GetOptimizeInfo(); EXPECT_THAT(optimize_info.status(), ProtoIsOk()); EXPECT_THAT(optimize_info.optimizable_docs(), Eq(0)); EXPECT_THAT(optimize_info.estimated_optimizable_bytes(), Eq(0)); EXPECT_THAT(optimize_info.time_since_last_optimize_ms(), Eq(4000)); } } TEST_F(IcingSearchEngineOptimizeTest, GetAndPutShouldWorkAfterOptimization) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document3 = DocumentBuilder() .SetKey("namespace", "uri3") .SetSchema("Message") .AddStringProperty("body", "message body three") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document4 = DocumentBuilder() .SetKey("namespace", "uri4") .SetSchema("Message") .AddStringProperty("body", "message body four") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document5 = DocumentBuilder() .SetKey("namespace", "uri5") .SetSchema("Message") .AddStringProperty("body", "message body five") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::OK); { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk()); ASSERT_THAT(icing.Delete("namespace", "uri2").status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that Get() and Put() are good right after Optimize() *expected_get_result_proto.mutable_document() = document1; EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); EXPECT_THAT( icing.Get("namespace", "uri2", GetResultSpecProto::default_instance()) .status() .code(), Eq(StatusProto::NOT_FOUND)); *expected_get_result_proto.mutable_document() = document3; EXPECT_THAT( icing.Get("namespace", "uri3", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); EXPECT_THAT(icing.Put(document4).status(), ProtoIsOk()); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); *expected_get_result_proto.mutable_document() = document1; EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); EXPECT_THAT( icing.Get("namespace", "uri2", GetResultSpecProto::default_instance()) .status() .code(), Eq(StatusProto::NOT_FOUND)); *expected_get_result_proto.mutable_document() = document3; EXPECT_THAT( icing.Get("namespace", "uri3", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); *expected_get_result_proto.mutable_document() = document4; EXPECT_THAT( icing.Get("namespace", "uri4", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); EXPECT_THAT(icing.Put(document5).status(), ProtoIsOk()); } TEST_F(IcingSearchEngineOptimizeTest, GetAndPutShouldWorkAfterOptimizationWithEmptyDocuments) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto empty_document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto empty_document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto empty_document3 = DocumentBuilder() .SetKey("namespace", "uri3") .SetSchema("Message") .AddStringProperty("body", "") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::OK); IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(std::move(schema)).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(empty_document1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(empty_document2).status(), ProtoIsOk()); ASSERT_THAT(icing.Delete("namespace", "uri2").status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that Get() and Put() are good right after Optimize() *expected_get_result_proto.mutable_document() = empty_document1; EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); EXPECT_THAT( icing.Get("namespace", "uri2", GetResultSpecProto::default_instance()) .status() .code(), Eq(StatusProto::NOT_FOUND)); EXPECT_THAT(icing.Put(empty_document3).status(), ProtoIsOk()); } TEST_F(IcingSearchEngineOptimizeTest, DeleteShouldWorkAfterOptimization) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that Delete() works right after Optimize() EXPECT_THAT(icing.Delete("namespace", "uri1").status(), ProtoIsOk()); GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code( StatusProto::NOT_FOUND); expected_get_result_proto.mutable_status()->set_message( "Document (namespace, uri1) not found."); EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); expected_get_result_proto.mutable_status()->set_code(StatusProto::OK); expected_get_result_proto.mutable_status()->clear_message(); *expected_get_result_proto.mutable_document() = document2; EXPECT_THAT( icing.Get("namespace", "uri2", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); EXPECT_THAT(icing.Delete("namespace", "uri2").status(), ProtoIsOk()); GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND); expected_get_result_proto.mutable_status()->set_message( "Document (namespace, uri1) not found."); EXPECT_THAT( icing.Get("namespace", "uri1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); expected_get_result_proto.mutable_status()->set_message( "Document (namespace, uri2) not found."); EXPECT_THAT( icing.Get("namespace", "uri2", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizationFailureUninitializesIcing) { // Setup filesystem to fail auto mock_filesystem = std::make_unique(); bool just_swapped_files = false; auto create_dir_lambda = [this, &just_swapped_files](const char* dir_name) { if (just_swapped_files) { // We should fail the first call immediately after swapping files. just_swapped_files = false; return false; } return filesystem()->CreateDirectoryRecursively(dir_name); }; ON_CALL(*mock_filesystem, CreateDirectoryRecursively) .WillByDefault(create_dir_lambda); auto swap_lambda = [&just_swapped_files](const char* first_dir, const char* second_dir) { just_swapped_files = true; return false; }; IcingSearchEngineOptions options = GetDefaultIcingOptions(); ON_CALL(*mock_filesystem, SwapFiles(HasSubstr("document_dir_optimize_tmp"), HasSubstr("document_dir"))) .WillByDefault(swap_lambda); TestIcingSearchEngine icing(options, std::move(mock_filesystem), std::make_unique(), std::make_unique(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); // The mocks should cause an unrecoverable error during Optimize - returning // INTERNAL. ASSERT_THAT(icing.Optimize().status(), ProtoStatusIs(StatusProto::INTERNAL)); // Ordinary operations should fail safely. SchemaProto simple_schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("type0").AddProperty( PropertyConfigBuilder() .SetName("prop0") .SetDataType(TYPE_STRING) .SetCardinality(CARDINALITY_OPTIONAL))) .Build(); DocumentProto simple_doc = DocumentBuilder() .SetKey("namespace0", "uri0") .SetSchema("type0") .AddStringProperty("prop0", "foo") .Build(); SearchSpecProto search_spec; search_spec.set_query("foo"); search_spec.set_term_match_type(TermMatchType::EXACT_ONLY); ResultSpecProto result_spec; ScoringSpecProto scoring_spec; scoring_spec.set_rank_by( ScoringSpecProto::RankingStrategy::CREATION_TIMESTAMP); EXPECT_THAT(icing.SetSchema(simple_schema).status(), ProtoStatusIs(StatusProto::FAILED_PRECONDITION)); EXPECT_THAT(icing.Put(simple_doc).status(), ProtoStatusIs(StatusProto::FAILED_PRECONDITION)); EXPECT_THAT(icing .Get(simple_doc.namespace_(), simple_doc.uri(), GetResultSpecProto::default_instance()) .status(), ProtoStatusIs(StatusProto::FAILED_PRECONDITION)); EXPECT_THAT(icing.Search(search_spec, scoring_spec, result_spec).status(), ProtoStatusIs(StatusProto::FAILED_PRECONDITION)); // Reset should get icing back to a safe (empty) and working state. EXPECT_THAT(icing.Reset().status(), ProtoIsOk()); EXPECT_THAT(icing.SetSchema(simple_schema).status(), ProtoIsOk()); EXPECT_THAT(icing.Put(simple_doc).status(), ProtoIsOk()); EXPECT_THAT(icing .Get(simple_doc.namespace_(), simple_doc.uri(), GetResultSpecProto::default_instance()) .status(), ProtoIsOk()); EXPECT_THAT(icing.Search(search_spec, scoring_spec, result_spec).status(), ProtoIsOk()); } TEST_F(IcingSearchEngineOptimizeTest, SetSchemaShouldWorkAfterOptimization) { // Creates 3 test schemas SchemaProto schema1 = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); SchemaProto schema2 = SchemaProto(schema1); *schema2.mutable_types(0)->add_properties() = PropertyConfigBuilder() .SetName("property2") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_OPTIONAL) .Build(); SchemaProto schema3 = SchemaProto(schema2); *schema3.mutable_types(0)->add_properties() = PropertyConfigBuilder() .SetName("property3") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_OPTIONAL) .Build(); { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema1).status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that SetSchema() works right after Optimize() EXPECT_THAT(icing.SetSchema(schema2).status(), ProtoIsOk()); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); EXPECT_THAT(icing.SetSchema(schema3).status(), ProtoIsOk()); } TEST_F(IcingSearchEngineOptimizeTest, SearchShouldWorkAfterOptimization) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("indexableInteger") .SetDataTypeInt64(NUMERIC_MATCH_RANGE) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document = DocumentBuilder() .SetKey("namespace", "uri") .SetSchema("Message") .AddStringProperty("body", "message body") .AddInt64Property("indexableInteger", 123) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); SearchSpecProto search_spec1; search_spec1.set_term_match_type(TermMatchType::PREFIX); search_spec1.set_query("m"); SearchSpecProto search_spec2; search_spec2.set_query("indexableInteger == 123"); search_spec2.set_search_type( SearchSpecProto::SearchType::EXPERIMENTAL_ICING_ADVANCED_QUERY); search_spec2.add_enabled_features(std::string(kNumericSearchFeature)); SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); *expected_search_result_proto.mutable_results()->Add()->mutable_document() = document; { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document).status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that Search() works right after Optimize() // Term search SearchResultProto search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Numeric (integer) search SearchResultProto search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); // Verify term search SearchResultProto search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Verify numeric (integer) search SearchResultProto search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, JoinShouldWorkAfterOptimizationDeleteParent) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( PropertyConfigBuilder() .SetName("name") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("senderQualifiedId") .SetDataTypeJoinableString( JOINABLE_VALUE_TYPE_QUALIFIED_ID) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto person1 = DocumentBuilder() .SetKey("namespace", "person1") .SetSchema("Person") .AddStringProperty("name", "person one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto person2 = DocumentBuilder() .SetKey("namespace", "person2") .SetSchema("Person") .AddStringProperty("name", "person two") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message1 = DocumentBuilder() .SetKey("namespace", "message1") .SetSchema("Message") .AddStringProperty("body", "message body one") .AddStringProperty("senderQualifiedId", "namespace#person1") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message2 = DocumentBuilder() .SetKey("namespace", "message2") .SetSchema("Message") .AddStringProperty("body", "message body two") .AddStringProperty("senderQualifiedId", "namespace#person1") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message3 = DocumentBuilder() .SetKey("namespace", "message3") .SetSchema("Message") .AddStringProperty("body", "message body three") .AddStringProperty("senderQualifiedId", "namespace#person2") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); // Prepare join search spec to join a query for `name:person` with a child // query for `body:message` based on the child's `senderQualifiedId` field. SearchSpecProto search_spec; search_spec.set_term_match_type(TermMatchType::EXACT_ONLY); search_spec.set_query("name:person"); JoinSpecProto* join_spec = search_spec.mutable_join_spec(); join_spec->set_parent_property_expression( std::string(JoinProcessor::kQualifiedIdExpr)); join_spec->set_child_property_expression("senderQualifiedId"); join_spec->set_aggregation_scoring_strategy( JoinSpecProto::AggregationScoringStrategy::COUNT); JoinSpecProto::NestedSpecProto* nested_spec = join_spec->mutable_nested_spec(); SearchSpecProto* nested_search_spec = nested_spec->mutable_search_spec(); nested_search_spec->set_term_match_type(TermMatchType::EXACT_ONLY); nested_search_spec->set_query("body:message"); *nested_spec->mutable_scoring_spec() = GetDefaultScoringSpec(); *nested_spec->mutable_result_spec() = ResultSpecProto::default_instance(); ResultSpecProto result_spec = ResultSpecProto::default_instance(); result_spec.set_max_joined_children_per_parent_to_return( std::numeric_limits::max()); // Person1 is going to be deleted below. Only person2 which is joined with // message3 should match the query. SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); SearchResultProto::ResultProto* result_proto = expected_search_result_proto.mutable_results()->Add(); *result_proto->mutable_document() = person2; *result_proto->mutable_joined_results()->Add()->mutable_document() = message3; { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(person1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(person2).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message2).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message3).status(), ProtoIsOk()); // Delete parent document: person1 ASSERT_THAT(icing.Delete("namespace", "person1").status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that join search query works right after Optimize() SearchResultProto search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); SearchResultProto search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, JoinShouldWorkAfterOptimizationDeleteChild) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( PropertyConfigBuilder() .SetName("name") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("senderQualifiedId") .SetDataTypeJoinableString( JOINABLE_VALUE_TYPE_QUALIFIED_ID) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto person1 = DocumentBuilder() .SetKey("namespace", "person1") .SetSchema("Person") .AddStringProperty("name", "person one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto person2 = DocumentBuilder() .SetKey("namespace", "person2") .SetSchema("Person") .AddStringProperty("name", "person two") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message1 = DocumentBuilder() .SetKey("namespace", "message1") .SetSchema("Message") .AddStringProperty("body", "message body one") .AddStringProperty("senderQualifiedId", "namespace#person1") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message2 = DocumentBuilder() .SetKey("namespace", "message2") .SetSchema("Message") .AddStringProperty("body", "message body two") .AddStringProperty("senderQualifiedId", "namespace#person1") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message3 = DocumentBuilder() .SetKey("namespace", "message3") .SetSchema("Message") .AddStringProperty("body", "message body three") .AddStringProperty("senderQualifiedId", "namespace#person2") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); // Prepare join search spec to join a query for `name:person` with a child // query for `body:message` based on the child's `senderQualifiedId` field. SearchSpecProto search_spec; search_spec.set_term_match_type(TermMatchType::EXACT_ONLY); search_spec.set_query("name:person"); JoinSpecProto* join_spec = search_spec.mutable_join_spec(); join_spec->set_parent_property_expression( std::string(JoinProcessor::kQualifiedIdExpr)); join_spec->set_child_property_expression("senderQualifiedId"); join_spec->set_aggregation_scoring_strategy( JoinSpecProto::AggregationScoringStrategy::COUNT); JoinSpecProto::NestedSpecProto* nested_spec = join_spec->mutable_nested_spec(); SearchSpecProto* nested_search_spec = nested_spec->mutable_search_spec(); nested_search_spec->set_term_match_type(TermMatchType::EXACT_ONLY); nested_search_spec->set_query("body:message"); *nested_spec->mutable_scoring_spec() = GetDefaultScoringSpec(); *nested_spec->mutable_result_spec() = ResultSpecProto::default_instance(); ResultSpecProto result_spec = ResultSpecProto::default_instance(); result_spec.set_max_joined_children_per_parent_to_return( std::numeric_limits::max()); // Message1 and message3 are going to be deleted below. Both person1 and // person2 should be included even though person2 has no child (since we're // doing left join). SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); SearchResultProto::ResultProto* result_proto1 = expected_search_result_proto.mutable_results()->Add(); *result_proto1->mutable_document() = person1; *result_proto1->mutable_joined_results()->Add()->mutable_document() = message2; SearchResultProto::ResultProto* result_google::protobuf = expected_search_result_proto.mutable_results()->Add(); *result_google::protobuf->mutable_document() = person2; { IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(person1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(person2).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message2).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message3).status(), ProtoIsOk()); // Delete child documents: message1 and message3 ASSERT_THAT(icing.Delete("namespace", "message1").status(), ProtoIsOk()); ASSERT_THAT(icing.Delete("namespace", "message3").status(), ProtoIsOk()); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); // Validates that join search query works right after Optimize() SearchResultProto search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } // Destroys IcingSearchEngine to make sure nothing is cached. IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); SearchResultProto search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(), result_spec); EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, IcingShouldWorkFineIfOptimizationIsAborted) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( PropertyConfigBuilder() .SetName("name") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("indexableInteger") .SetDataTypeInt64(NUMERIC_MATCH_RANGE) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("senderQualifiedId") .SetDataTypeJoinableString( JOINABLE_VALUE_TYPE_QUALIFIED_ID) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto person = DocumentBuilder() .SetKey("namespace", "person") .SetSchema("Person") .AddStringProperty("name", "person") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto message1 = DocumentBuilder() .SetKey("namespace", "message1") .SetSchema("Message") .AddStringProperty("body", "message body one") .AddInt64Property("indexableInteger", 123) .AddStringProperty("senderQualifiedId", "namespace#person") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); { // Initializes a normal icing to create files needed IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(person).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(message1).status(), ProtoIsOk()); } // Creates a mock filesystem in which DeleteDirectoryRecursively() always // fails. This will fail IcingSearchEngine::OptimizeDocumentStore() and makes // it return ABORTED_ERROR. auto mock_filesystem = std::make_unique(); ON_CALL(*mock_filesystem, DeleteDirectoryRecursively(HasSubstr("_optimize_tmp"))) .WillByDefault(Return(false)); TestIcingSearchEngine icing(GetDefaultIcingOptions(), std::move(mock_filesystem), std::make_unique(), std::make_unique(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); EXPECT_THAT(icing.Optimize().status(), ProtoStatusIs(StatusProto::ABORTED)); // Now optimization is aborted, we verify that document-related functions // still work as expected. GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::OK); *expected_get_result_proto.mutable_document() = message1; EXPECT_THAT(icing.Get("namespace", "message1", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); DocumentProto message2 = DocumentBuilder() .SetKey("namespace", "message2") .SetSchema("Message") .AddStringProperty("body", "message body two") .AddInt64Property("indexableInteger", 123) .AddStringProperty("senderQualifiedId", "namespace#person") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); EXPECT_THAT(icing.Put(message2).status(), ProtoIsOk()); SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); *expected_search_result_proto.mutable_results()->Add()->mutable_document() = message2; *expected_search_result_proto.mutable_results()->Add()->mutable_document() = message1; // Verify term search SearchSpecProto search_spec1; search_spec1.set_query("body:m"); search_spec1.set_term_match_type(TermMatchType::PREFIX); SearchResultProto search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Verify numeric (integer) search SearchSpecProto search_spec2; search_spec2.set_query("indexableInteger == 123"); search_spec2.set_search_type( SearchSpecProto::SearchType::EXPERIMENTAL_ICING_ADVANCED_QUERY); search_spec2.add_enabled_features(std::string(kNumericSearchFeature)); SearchResultProto search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Verify join search: join a query for `name:person` with a child query for // `body:message` based on the child's `senderQualifiedId` field. SearchSpecProto search_spec3; search_spec3.set_term_match_type(TermMatchType::EXACT_ONLY); search_spec3.set_query("name:person"); JoinSpecProto* join_spec = search_spec3.mutable_join_spec(); join_spec->set_parent_property_expression( std::string(JoinProcessor::kQualifiedIdExpr)); join_spec->set_child_property_expression("senderQualifiedId"); join_spec->set_aggregation_scoring_strategy( JoinSpecProto::AggregationScoringStrategy::COUNT); JoinSpecProto::NestedSpecProto* nested_spec = join_spec->mutable_nested_spec(); SearchSpecProto* nested_search_spec = nested_spec->mutable_search_spec(); nested_search_spec->set_term_match_type(TermMatchType::EXACT_ONLY); nested_search_spec->set_query("body:message"); *nested_spec->mutable_scoring_spec() = GetDefaultScoringSpec(); *nested_spec->mutable_result_spec() = ResultSpecProto::default_instance(); ResultSpecProto result_spec3 = ResultSpecProto::default_instance(); result_spec3.set_max_joined_children_per_parent_to_return( std::numeric_limits::max()); SearchResultProto expected_join_search_result_proto; expected_join_search_result_proto.mutable_status()->set_code(StatusProto::OK); SearchResultProto::ResultProto* result_proto = expected_join_search_result_proto.mutable_results()->Add(); *result_proto->mutable_document() = person; *result_proto->mutable_joined_results()->Add()->mutable_document() = message2; *result_proto->mutable_joined_results()->Add()->mutable_document() = message1; SearchResultProto search_result_proto3 = icing.Search(search_spec3, GetDefaultScoringSpec(), result_spec3); EXPECT_THAT(search_result_proto3, EqualsSearchResultIgnoreStatsAndScores( expected_join_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizationShouldRecoverIfFileDirectoriesAreMissing) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("indexableInteger") .SetDataTypeInt64(NUMERIC_MATCH_RANGE) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body") .AddInt64Property("indexableInteger", 123) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); // Creates a mock filesystem in which SwapFiles() always fails and deletes the // directories. This will fail IcingSearchEngine::OptimizeDocumentStore(). auto mock_filesystem = std::make_unique(); ON_CALL(*mock_filesystem, SwapFiles(HasSubstr("document_dir_optimize_tmp"), HasSubstr("document_dir"))) .WillByDefault([this](const char* one, const char* two) { filesystem()->DeleteDirectoryRecursively(one); filesystem()->DeleteDirectoryRecursively(two); return false; }); TestIcingSearchEngine icing(GetDefaultIcingOptions(), std::move(mock_filesystem), std::make_unique(), std::make_unique(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document).status(), ProtoIsOk()); // Optimize() fails due to filesystem error OptimizeResultProto result = icing.Optimize(); EXPECT_THAT(result.status(), ProtoStatusIs(StatusProto::WARNING_DATA_LOSS)); // Should rebuild the index for data loss. EXPECT_THAT(result.optimize_stats().index_restoration_mode(), Eq(OptimizeStatsProto::FULL_INDEX_REBUILD)); // Document is not found because original file directory is missing GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND); expected_get_result_proto.mutable_status()->set_message( "Document (namespace, uri) not found."); EXPECT_THAT( icing.Get("namespace", "uri", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); DocumentProto new_document = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "new body") .AddInt64Property("indexableInteger", 456) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); EXPECT_THAT(icing.Put(new_document).status(), ProtoIsOk()); SearchSpecProto search_spec1; search_spec1.set_query("m"); search_spec1.set_term_match_type(TermMatchType::PREFIX); SearchSpecProto search_spec2; search_spec2.set_query("indexableInteger == 123"); search_spec2.set_search_type( SearchSpecProto::SearchType::EXPERIMENTAL_ICING_ADVANCED_QUERY); search_spec2.add_enabled_features(std::string(kNumericSearchFeature)); SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); // Searching old content returns nothing because original file directory is // missing // Term search SearchResultProto search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Numeric (integer) search SearchResultProto search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Searching new content returns the new document *expected_search_result_proto.mutable_results()->Add()->mutable_document() = new_document; // Term search search_spec1.set_query("n"); search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Numeric (integer) search search_spec2.set_query("indexableInteger == 456"); search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizationShouldRecoverIfDataFilesAreMissing) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("indexableInteger") .SetDataTypeInt64(NUMERIC_MATCH_RANGE) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body") .AddInt64Property("indexableInteger", 123) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); // Creates a mock filesystem in which SwapFiles() always fails and empties the // directories. This will fail IcingSearchEngine::OptimizeDocumentStore(). auto mock_filesystem = std::make_unique(); ON_CALL(*mock_filesystem, SwapFiles(HasSubstr("document_dir_optimize_tmp"), HasSubstr("document_dir"))) .WillByDefault([this](const char* one, const char* two) { filesystem()->DeleteDirectoryRecursively(one); filesystem()->CreateDirectoryRecursively(one); filesystem()->DeleteDirectoryRecursively(two); filesystem()->CreateDirectoryRecursively(two); return false; }); TestIcingSearchEngine icing(GetDefaultIcingOptions(), std::move(mock_filesystem), std::make_unique(), std::make_unique(), GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document).status(), ProtoIsOk()); // Optimize() fails due to filesystem error OptimizeResultProto result = icing.Optimize(); EXPECT_THAT(result.status(), ProtoStatusIs(StatusProto::WARNING_DATA_LOSS)); // Should rebuild the index for data loss. EXPECT_THAT(result.optimize_stats().index_restoration_mode(), Eq(OptimizeStatsProto::FULL_INDEX_REBUILD)); // Document is not found because original files are missing GetResultProto expected_get_result_proto; expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND); expected_get_result_proto.mutable_status()->set_message( "Document (namespace, uri) not found."); EXPECT_THAT( icing.Get("namespace", "uri", GetResultSpecProto::default_instance()), EqualsProto(expected_get_result_proto)); DocumentProto new_document = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "new body") .AddInt64Property("indexableInteger", 456) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); EXPECT_THAT(icing.Put(new_document).status(), ProtoIsOk()); SearchSpecProto search_spec1; search_spec1.set_query("m"); search_spec1.set_term_match_type(TermMatchType::PREFIX); SearchSpecProto search_spec2; search_spec2.set_query("indexableInteger == 123"); search_spec2.set_search_type( SearchSpecProto::SearchType::EXPERIMENTAL_ICING_ADVANCED_QUERY); search_spec2.add_enabled_features(std::string(kNumericSearchFeature)); SearchResultProto expected_search_result_proto; expected_search_result_proto.mutable_status()->set_code(StatusProto::OK); // Searching old content returns nothing because original files are missing // Term search SearchResultProto search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Numeric (integer) search SearchResultProto search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Searching new content returns the new document *expected_search_result_proto.mutable_results()->Add()->mutable_document() = new_document; // Term search search_spec1.set_query("n"); search_result_proto1 = icing.Search(search_spec1, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_proto1, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); // Numeric (integer) search search_spec2.set_query("indexableInteger == 456"); search_result_google::protobuf = icing.Search(search_spec2, GetDefaultScoringSpec(), ResultSpecProto::default_instance()); EXPECT_THAT(search_result_google::protobuf, EqualsSearchResultIgnoreStatsAndScores( expected_search_result_proto)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizeThresholdTest) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("indexableInteger") .SetDataTypeInt64(NUMERIC_MATCH_RANGE) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .AddInt64Property("indexableInteger", 1) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .AddInt64Property("indexableInteger", 2) .SetCreationTimestampMs(9000) .SetTtlMs(500) .Build(); DocumentProto document3 = DocumentBuilder() .SetKey("namespace", "uri3") .SetSchema("Message") .AddStringProperty("body", "message body three") .AddInt64Property("indexableInteger", 3) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); auto fake_clock = std::make_unique(); fake_clock->SetTimerElapsedMilliseconds(5); fake_clock->SetSystemTimeMilliseconds(10000); IcingSearchEngineOptions options = GetDefaultIcingOptions(); // Set the threshold to 0.9 to test that the threshold works. options.set_optimize_rebuild_index_threshold(0.9); auto icing = std::make_unique( options, std::make_unique(), std::make_unique(), std::move(fake_clock), GetTestJniCache()); ASSERT_THAT(icing->Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing->SetSchema(schema).status(), ProtoIsOk()); // Add three documents. ASSERT_THAT(icing->Put(document1).status(), ProtoIsOk()); ASSERT_THAT(icing->Put(document2).status(), ProtoIsOk()); ASSERT_THAT(icing->Put(document3).status(), ProtoIsOk()); // Delete the first document. ASSERT_THAT(icing->Delete(document1.namespace_(), document1.uri()).status(), ProtoIsOk()); ASSERT_THAT(icing->PersistToDisk(PersistType::FULL).status(), ProtoIsOk()); OptimizeStatsProto expected; expected.set_latency_ms(5); expected.set_document_store_optimize_latency_ms(5); expected.set_index_restoration_latency_ms(5); expected.set_num_original_documents(3); expected.set_num_deleted_documents(1); expected.set_num_expired_documents(1); expected.set_index_restoration_mode(OptimizeStatsProto::INDEX_TRANSLATION); // Run Optimize OptimizeResultProto result = icing->Optimize(); // Depending on how many blocks the documents end up spread across, it's // possible that Optimize can remove documents without shrinking storage. The // first Optimize call will also write the OptimizeStatusProto for the first // time which will take up 1 block. So make sure that before_size is no less // than after_size - 1 block. uint32_t page_size = getpagesize(); EXPECT_THAT(result.optimize_stats().storage_size_before(), Ge(result.optimize_stats().storage_size_after() - page_size)); result.mutable_optimize_stats()->clear_storage_size_before(); result.mutable_optimize_stats()->clear_storage_size_after(); EXPECT_THAT(result.optimize_stats(), EqualsProto(expected)); fake_clock = std::make_unique(); fake_clock->SetTimerElapsedMilliseconds(5); fake_clock->SetSystemTimeMilliseconds(20000); icing = std::make_unique( options, std::make_unique(), std::make_unique(), std::move(fake_clock), GetTestJniCache()); ASSERT_THAT(icing->Initialize().status(), ProtoIsOk()); expected = OptimizeStatsProto(); expected.set_latency_ms(5); expected.set_document_store_optimize_latency_ms(5); expected.set_index_restoration_latency_ms(5); expected.set_num_original_documents(1); expected.set_num_deleted_documents(0); expected.set_num_expired_documents(0); expected.set_time_since_last_optimize_ms(10000); expected.set_index_restoration_mode(OptimizeStatsProto::INDEX_TRANSLATION); // Run Optimize result = icing->Optimize(); EXPECT_THAT(result.optimize_stats().storage_size_before(), Eq(result.optimize_stats().storage_size_after())); result.mutable_optimize_stats()->clear_storage_size_before(); result.mutable_optimize_stats()->clear_storage_size_after(); EXPECT_THAT(result.optimize_stats(), EqualsProto(expected)); // Delete the last document. ASSERT_THAT(icing->Delete(document3.namespace_(), document3.uri()).status(), ProtoIsOk()); expected = OptimizeStatsProto(); expected.set_latency_ms(5); expected.set_document_store_optimize_latency_ms(5); expected.set_index_restoration_latency_ms(5); expected.set_num_original_documents(1); expected.set_num_deleted_documents(1); expected.set_num_expired_documents(0); expected.set_time_since_last_optimize_ms(0); // Should rebuild the index since all documents are removed. expected.set_index_restoration_mode(OptimizeStatsProto::FULL_INDEX_REBUILD); // Run Optimize result = icing->Optimize(); EXPECT_THAT(result.optimize_stats().storage_size_before(), Ge(result.optimize_stats().storage_size_after())); result.mutable_optimize_stats()->clear_storage_size_before(); result.mutable_optimize_stats()->clear_storage_size_after(); EXPECT_THAT(result.optimize_stats(), EqualsProto(expected)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizeStatsProtoTest) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder() .SetType("Message") .AddProperty(PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED)) .AddProperty(PropertyConfigBuilder() .SetName("indexableInteger") .SetDataTypeInt64(NUMERIC_MATCH_RANGE) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .AddInt64Property("indexableInteger", 1) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .AddInt64Property("indexableInteger", 2) .SetCreationTimestampMs(9000) .SetTtlMs(500) .Build(); DocumentProto document3 = DocumentBuilder() .SetKey("namespace", "uri3") .SetSchema("Message") .AddStringProperty("body", "message body three") .AddInt64Property("indexableInteger", 3) .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); auto fake_clock = std::make_unique(); fake_clock->SetTimerElapsedMilliseconds(5); fake_clock->SetSystemTimeMilliseconds(10000); // Use the default Icing options, so that a change to the default value will // require updating this test. auto icing = std::make_unique( GetDefaultIcingOptions(), std::make_unique(), std::make_unique(), std::move(fake_clock), GetTestJniCache()); ASSERT_THAT(icing->Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing->SetSchema(schema).status(), ProtoIsOk()); // Add three documents. ASSERT_THAT(icing->Put(document1).status(), ProtoIsOk()); ASSERT_THAT(icing->Put(document2).status(), ProtoIsOk()); ASSERT_THAT(icing->Put(document3).status(), ProtoIsOk()); // Delete the first document. ASSERT_THAT(icing->Delete(document1.namespace_(), document1.uri()).status(), ProtoIsOk()); ASSERT_THAT(icing->PersistToDisk(PersistType::FULL).status(), ProtoIsOk()); OptimizeStatsProto expected; expected.set_latency_ms(5); expected.set_document_store_optimize_latency_ms(5); expected.set_index_restoration_latency_ms(5); expected.set_num_original_documents(3); expected.set_num_deleted_documents(1); expected.set_num_expired_documents(1); expected.set_index_restoration_mode(OptimizeStatsProto::FULL_INDEX_REBUILD); // Run Optimize OptimizeResultProto result = icing->Optimize(); // Depending on how many blocks the documents end up spread across, it's // possible that Optimize can remove documents without shrinking storage. The // first Optimize call will also write the OptimizeStatusProto for the first // time which will take up 1 block. So make sure that before_size is no less // than after_size - 1 block. uint32_t page_size = getpagesize(); EXPECT_THAT(result.optimize_stats().storage_size_before(), Ge(result.optimize_stats().storage_size_after() - page_size)); result.mutable_optimize_stats()->clear_storage_size_before(); result.mutable_optimize_stats()->clear_storage_size_after(); EXPECT_THAT(result.optimize_stats(), EqualsProto(expected)); fake_clock = std::make_unique(); fake_clock->SetTimerElapsedMilliseconds(5); fake_clock->SetSystemTimeMilliseconds(20000); // Use the default Icing options, so that a change to the default value will // require updating this test. icing = std::make_unique( GetDefaultIcingOptions(), std::make_unique(), std::make_unique(), std::move(fake_clock), GetTestJniCache()); ASSERT_THAT(icing->Initialize().status(), ProtoIsOk()); expected = OptimizeStatsProto(); expected.set_latency_ms(5); expected.set_document_store_optimize_latency_ms(5); expected.set_index_restoration_latency_ms(5); expected.set_num_original_documents(1); expected.set_num_deleted_documents(0); expected.set_num_expired_documents(0); expected.set_time_since_last_optimize_ms(10000); expected.set_index_restoration_mode(OptimizeStatsProto::FULL_INDEX_REBUILD); // Run Optimize result = icing->Optimize(); EXPECT_THAT(result.optimize_stats().storage_size_before(), Eq(result.optimize_stats().storage_size_after())); result.mutable_optimize_stats()->clear_storage_size_before(); result.mutable_optimize_stats()->clear_storage_size_after(); EXPECT_THAT(result.optimize_stats(), EqualsProto(expected)); // Delete the last document. ASSERT_THAT(icing->Delete(document3.namespace_(), document3.uri()).status(), ProtoIsOk()); expected = OptimizeStatsProto(); expected.set_latency_ms(5); expected.set_document_store_optimize_latency_ms(5); expected.set_index_restoration_latency_ms(5); expected.set_num_original_documents(1); expected.set_num_deleted_documents(1); expected.set_num_expired_documents(0); expected.set_time_since_last_optimize_ms(0); expected.set_index_restoration_mode(OptimizeStatsProto::FULL_INDEX_REBUILD); // Run Optimize result = icing->Optimize(); EXPECT_THAT(result.optimize_stats().storage_size_before(), Ge(result.optimize_stats().storage_size_after())); result.mutable_optimize_stats()->clear_storage_size_before(); result.mutable_optimize_stats()->clear_storage_size_after(); EXPECT_THAT(result.optimize_stats(), EqualsProto(expected)); } TEST_F(IcingSearchEngineOptimizeTest, OptimizationRewritesDocsWithNewCompressionLevel) { SchemaProto schema = SchemaBuilder() .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( PropertyConfigBuilder() .SetName("body") .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) .SetCardinality(CARDINALITY_REQUIRED))) .Build(); DocumentProto document1 = DocumentBuilder() .SetKey("namespace", "uri1") .SetSchema("Message") .AddStringProperty("body", "message body one") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); DocumentProto document2 = DocumentBuilder() .SetKey("namespace", "uri2") .SetSchema("Message") .AddStringProperty("body", "message body two") .SetCreationTimestampMs(kDefaultCreationTimestampMs) .Build(); IcingSearchEngineOptions icing_options = GetDefaultIcingOptions(); icing_options.set_compression_level(3); int64_t document_log_size_compression_3; int64_t document_log_size_after_opti_no_compression; int64_t document_log_size_after_opti_compression_3; const std::string document_log_path = icing_options.base_dir() + "/document_dir/" + DocumentLogCreator::GetDocumentLogFilename(); { IcingSearchEngine icing(icing_options, GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); ASSERT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk()); document_log_size_compression_3 = filesystem()->GetFileSize(document_log_path.c_str()); } // Destroys IcingSearchEngine to make sure nothing is cached. // Turn off compression icing_options.set_compression_level(0); { IcingSearchEngine icing(icing_options, GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); // Document log size is the same even after reopening with a different // compression level ASSERT_EQ(document_log_size_compression_3, filesystem()->GetFileSize(document_log_path.c_str())); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); document_log_size_after_opti_no_compression = filesystem()->GetFileSize(document_log_path.c_str()); // Document log size is larger after optimizing since optimizing rewrites // with the new compression level which is 0 or none ASSERT_GT(document_log_size_after_opti_no_compression, document_log_size_compression_3); } // Restore the original compression level icing_options.set_compression_level(3); { IcingSearchEngine icing(icing_options, GetTestJniCache()); ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); // Document log size is the same even after reopening with a different // compression level ASSERT_EQ(document_log_size_after_opti_no_compression, filesystem()->GetFileSize(document_log_path.c_str())); ASSERT_THAT(icing.Optimize().status(), ProtoIsOk()); document_log_size_after_opti_compression_3 = filesystem()->GetFileSize(document_log_path.c_str()); // Document log size should be the same as it was originally ASSERT_EQ(document_log_size_after_opti_compression_3, document_log_size_compression_3); } } } // namespace } // namespace lib } // namespace icing