blob: 61b594c8e14f04cc2087d3df4841039d8d313f18 [file] [edit]
// 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 <unistd.h>
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#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<const Filesystem> filesystem,
std::unique_ptr<const IcingFilesystem> icing_filesystem,
std::unique_ptr<Clock> clock,
std::unique_ptr<JniCache> 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<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
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<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(5000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
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<MockFilesystem>();
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<IcingFilesystem>(),
std::make_unique<FakeClock>(), 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<int32_t>::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<int32_t>::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<MockFilesystem>();
ON_CALL(*mock_filesystem,
DeleteDirectoryRecursively(HasSubstr("_optimize_tmp")))
.WillByDefault(Return(false));
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::move(mock_filesystem),
std::make_unique<IcingFilesystem>(),
std::make_unique<FakeClock>(), 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<int32_t>::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<MockFilesystem>();
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<IcingFilesystem>(),
std::make_unique<FakeClock>(), 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<MockFilesystem>();
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<IcingFilesystem>(),
std::make_unique<FakeClock>(), 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<FakeClock>();
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<TestIcingSearchEngine>(
options, std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(), 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_num_original_namespaces(1);
expected.set_num_deleted_namespaces(0);
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<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(5);
fake_clock->SetSystemTimeMilliseconds(20000);
icing = std::make_unique<TestIcingSearchEngine>(
options, std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(), 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_num_original_namespaces(1);
expected.set_num_deleted_namespaces(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_num_original_namespaces(1);
expected.set_num_deleted_namespaces(1);
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<FakeClock>();
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<TestIcingSearchEngine>(
GetDefaultIcingOptions(), std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(), 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_num_original_namespaces(1);
expected.set_num_deleted_namespaces(0);
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<FakeClock>();
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<TestIcingSearchEngine>(
GetDefaultIcingOptions(), std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(), 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_num_original_namespaces(1);
expected.set_num_deleted_namespaces(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_num_original_namespaces(1);
expected.set_num_deleted_namespaces(1);
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