blob: f4249f365111bb6de54fe96c8cd694f6fbd18f74 [file] [log] [blame]
// 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 "icing/icing-search-engine.h"
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include "icing/jni/jni-cache.h"
#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/helpers/icu/icu-data-file-helper.h"
#include "icing/legacy/index/icing-mock-filesystem.h"
#include "icing/portable/equals-proto.h"
#include "icing/proto/document.pb.h"
#include "icing/proto/initialize.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/schema/schema-store.h"
#include "icing/schema/section.h"
#include "icing/testing/common-matchers.h"
#include "icing/testing/fake-clock.h"
#include "icing/testing/jni-test-helpers.h"
#include "icing/testing/platform.h"
#include "icing/testing/random-string.h"
#include "icing/testing/snippet-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::_;
using ::testing::Eq;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Lt;
using ::testing::Matcher;
using ::testing::Ne;
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::StrEq;
using ::testing::UnorderedElementsAre;
constexpr std::string_view kIpsumText =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla convallis "
"scelerisque orci quis hendrerit. Sed augue turpis, sodales eu gravida "
"nec, scelerisque nec leo. Maecenas accumsan interdum commodo. Aliquam "
"mattis sapien est, sit amet interdum risus dapibus sed. Maecenas leo "
"erat, fringilla in nisl a, venenatis gravida metus. Phasellus venenatis, "
"orci in aliquet mattis, lectus sapien volutpat arcu, sed hendrerit ligula "
"arcu nec mauris. Integer dolor mi, rhoncus eget gravida et, pulvinar et "
"nunc. Aliquam ac sollicitudin nisi. Vivamus sit amet urna vestibulum, "
"tincidunt eros sed, efficitur nisl. Fusce non neque accumsan, sagittis "
"nisi eget, sagittis turpis. Ut pulvinar nibh eu purus feugiat faucibus. "
"Donec tellus nulla, tincidunt vel lacus id, bibendum fermentum turpis. "
"Nullam ultrices sed nibh vitae aliquet. Ut risus neque, consectetur "
"vehicula posuere vitae, convallis eu lorem. Donec semper augue eu nibh "
"placerat semper.";
// 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<FakeClock> 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"; }
class IcingSearchEngineTest : 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_;
};
constexpr int kMaxSupportedDocumentSize = (1u << 24) - 1;
// Non-zero value so we don't override it to be the current time
constexpr int64_t kDefaultCreationTimestampMs = 1575492852000;
std::string GetDocumentDir() { return GetTestBaseDir() + "/document_dir"; }
std::string GetIndexDir() { return GetTestBaseDir() + "/index_dir"; }
std::string GetSchemaDir() { return GetTestBaseDir() + "/schema_dir"; }
std::string GetHeaderFilename() {
return GetTestBaseDir() + "/icing_search_engine_header";
}
IcingSearchEngineOptions GetDefaultIcingOptions() {
IcingSearchEngineOptions icing_options;
icing_options.set_base_dir(GetTestBaseDir());
return icing_options;
}
DocumentProto CreateMessageDocument(std::string name_space, std::string uri) {
return DocumentBuilder()
.SetKey(std::move(name_space), std::move(uri))
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
}
SchemaProto CreateMessageSchema() {
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("Message");
auto body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
return schema;
}
SchemaProto CreateEmailSchema() {
SchemaProto schema;
auto* type = schema.add_types();
type->set_schema_type("Email");
auto* body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
auto* subj = type->add_properties();
subj->set_property_name("subject");
subj->set_data_type(PropertyConfigProto::DataType::STRING);
subj->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
subj->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
subj->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
return schema;
}
SchemaProto CreatePersonAndEmailSchema() {
SchemaProto schema;
auto* person_type = schema.add_types();
person_type->set_schema_type("Person");
auto* name = person_type->add_properties();
name->set_property_name("name");
name->set_data_type(PropertyConfigProto::DataType::STRING);
name->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
name->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
name->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
auto* address = person_type->add_properties();
address->set_property_name("emailAddress");
address->set_data_type(PropertyConfigProto::DataType::STRING);
address->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
address->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
address->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
auto* type = schema.add_types();
type->set_schema_type("Email");
auto* body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
auto* subj = type->add_properties();
subj->set_property_name("subject");
subj->set_data_type(PropertyConfigProto::DataType::STRING);
subj->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
subj->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
subj->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
auto* sender = type->add_properties();
sender->set_property_name("sender");
sender->set_schema_type("Person");
sender->set_data_type(PropertyConfigProto::DataType::DOCUMENT);
sender->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
sender->mutable_document_indexing_config()->set_index_nested_properties(true);
return schema;
}
ScoringSpecProto GetDefaultScoringSpec() {
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE);
return scoring_spec;
}
UsageReport CreateUsageReport(std::string name_space, std::string uri,
int64 timestamp_ms,
UsageReport::UsageType usage_type) {
UsageReport usage_report;
usage_report.set_document_namespace(name_space);
usage_report.set_document_uri(uri);
usage_report.set_usage_timestamp_ms(timestamp_ms);
usage_report.set_usage_type(usage_type);
return usage_report;
}
TEST_F(IcingSearchEngineTest, SimpleInitialization) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document = CreateMessageDocument("namespace", "uri");
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(DocumentProto(document)).status(), ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, InitializingAgainSavesNonPersistedData) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document = CreateMessageDocument("namespace", "uri");
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document;
ASSERT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, MaxIndexMergeSizeReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(std::numeric_limits<int32_t>::max());
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, NegativeMergeSizeReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(-1);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, ZeroMergeSizeReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(0);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, GoodIndexMergeSizeReturnsOk) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
// One is fine, if a bit weird. It just means that the lite index will be
// smaller and will request a merge any time content is added to it.
options.set_index_merge_size(1);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
}
TEST_F(IcingSearchEngineTest,
NegativeMaxTokensPerDocSizeReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_max_tokens_per_doc(-1);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, ZeroMaxTokensPerDocSizeReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_max_tokens_per_doc(0);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, GoodMaxTokensPerDocSizeReturnsOk) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
// INT_MAX is valid - it just means that we shouldn't limit the number of
// tokens per document. It would be pretty inconceivable that anyone would
// produce such a document - the text being indexed alone would take up at
// least ~4.3 GiB! - and the document would be rejected before indexing
// for exceeding max_document_size, but there's no reason to explicitly
// bar it.
options.set_max_tokens_per_doc(std::numeric_limits<int32_t>::max());
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, NegativeMaxTokenLenReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_max_token_length(-1);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, ZeroMaxTokenLenReturnsInvalidArgument) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_max_token_length(0);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, MaxTokenLenReturnsOkAndTruncatesTokens) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
// A length of 1 is allowed - even though it would be strange to want
// this.
options.set_max_token_length(1);
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document = CreateMessageDocument("namespace", "uri");
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// "message" should have been truncated to "m"
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
// The indexed tokens were truncated to length of 1, so "m" will match
search_spec.set_query("m");
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;
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// The query token is also truncated to length of 1, so "me"->"m" matches "m"
search_spec.set_query("me");
actual_results = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// The query token is still truncated to length of 1, so "massage"->"m"
// matches "m"
search_spec.set_query("massage");
actual_results = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest,
MaxIntMaxTokenLenReturnsOkTooLargeTokenReturnsResourceExhausted) {
IcingSearchEngineOptions options = GetDefaultIcingOptions();
// Set token length to max. This is allowed (it just means never to
// truncate tokens). However, this does mean that tokens that exceed the
// size of the lexicon will cause indexing to fail.
options.set_max_token_length(std::numeric_limits<int32_t>::max());
IcingSearchEngine icing(options, GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Add a document that just barely fits under the max document limit.
// This will still fail to index because we won't actually have enough
// room in the lexicon to fit this content.
std::string enormous_string(kMaxSupportedDocumentSize - 256, 'p');
DocumentProto document =
DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Message")
.AddStringProperty("body", std::move(enormous_string))
.Build();
EXPECT_THAT(icing.Put(document).status(),
ProtoStatusIs(StatusProto::OUT_OF_SPACE));
SearchSpecProto search_spec;
search_spec.set_query("p");
search_spec.set_term_match_type(TermMatchType::PREFIX);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, FailToCreateDocStore) {
auto mock_filesystem = std::make_unique<MockFilesystem>();
// This fails DocumentStore::Create()
ON_CALL(*mock_filesystem, CreateDirectoryRecursively(_))
.WillByDefault(Return(false));
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::move(mock_filesystem),
std::make_unique<IcingFilesystem>(),
std::make_unique<FakeClock>(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(),
ProtoStatusIs(StatusProto::INTERNAL));
EXPECT_THAT(initialize_result_proto.status().message(),
HasSubstr("Could not create directory"));
}
TEST_F(IcingSearchEngineTest,
CircularReferenceCreateSectionManagerReturnsInvalidArgument) {
// Create a type config with a circular reference.
SchemaProto schema;
auto* type = schema.add_types();
type->set_schema_type("Message");
auto* body = type->add_properties();
body->set_property_name("recipient");
body->set_schema_type("Person");
body->set_data_type(PropertyConfigProto::DataType::DOCUMENT);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_document_indexing_config()->set_index_nested_properties(true);
type = schema.add_types();
type->set_schema_type("Person");
body = type->add_properties();
body->set_property_name("recipient");
body->set_schema_type("Message");
body->set_data_type(PropertyConfigProto::DataType::DOCUMENT);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_document_indexing_config()->set_index_nested_properties(true);
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(schema).status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineTest, PutWithoutSchemaFailedPrecondition) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
DocumentProto document = CreateMessageDocument("namespace", "uri");
PutResultProto put_result_proto = icing.Put(document);
EXPECT_THAT(put_result_proto.status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(put_result_proto.status().message(), HasSubstr("Schema not set"));
}
TEST_F(IcingSearchEngineTest, FailToReadSchema) {
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
{
// Successfully initialize and set a schema
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
}
auto mock_filesystem = std::make_unique<MockFilesystem>();
// This fails FileBackedProto::Read() when we try to check the schema we
// had previously set
ON_CALL(*mock_filesystem,
OpenForRead(Eq(icing_options.base_dir() + "/schema_dir/schema.pb")))
.WillByDefault(Return(-1));
TestIcingSearchEngine test_icing(icing_options, std::move(mock_filesystem),
std::make_unique<IcingFilesystem>(),
std::make_unique<FakeClock>(),
GetTestJniCache());
InitializeResultProto initialize_result_proto = test_icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(),
ProtoStatusIs(StatusProto::INTERNAL));
EXPECT_THAT(initialize_result_proto.status().message(),
HasSubstr("Unable to open file for read"));
}
TEST_F(IcingSearchEngineTest, FailToWriteSchema) {
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
auto mock_filesystem = std::make_unique<MockFilesystem>();
// This fails FileBackedProto::Write()
ON_CALL(*mock_filesystem,
OpenForWrite(Eq(icing_options.base_dir() + "/schema_dir/schema.pb")))
.WillByDefault(Return(-1));
TestIcingSearchEngine icing(icing_options, std::move(mock_filesystem),
std::make_unique<IcingFilesystem>(),
std::make_unique<FakeClock>(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
SetSchemaResultProto set_schema_result_proto =
icing.SetSchema(CreateMessageSchema());
EXPECT_THAT(set_schema_result_proto.status(),
ProtoStatusIs(StatusProto::INTERNAL));
EXPECT_THAT(set_schema_result_proto.status().message(),
HasSubstr("Unable to open file for write"));
}
TEST_F(IcingSearchEngineTest, SetSchemaDelete2) {
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// 1. Create a schema with an Email type with properties { "title", "body"}
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("Email");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("title");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
// 2. Add an email document
DocumentProto doc = DocumentBuilder()
.SetKey("emails", "email#1")
.SetSchema("Email")
.AddStringProperty("title", "Hello world.")
.AddStringProperty("body", "Goodnight Moon.")
.Build();
EXPECT_THAT(icing.Put(std::move(doc)).status(), ProtoIsOk());
}
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// 3. Set a schema that deletes email. This should fail.
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("Message");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema, false).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
// 4. Try to delete by email type.
EXPECT_THAT(icing.DeleteBySchemaType("Email").status(), ProtoIsOk());
}
}
TEST_F(IcingSearchEngineTest, SetSchemaDelete) {
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// 1. Create a schema with an Email type with properties { "title", "body"}
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("Email");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("title");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
// 2. Add an email document
DocumentProto doc = DocumentBuilder()
.SetKey("emails", "email#1")
.SetSchema("Email")
.AddStringProperty("title", "Hello world.")
.AddStringProperty("body", "Goodnight Moon.")
.Build();
EXPECT_THAT(icing.Put(std::move(doc)).status(), ProtoIsOk());
}
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// 3. Set a schema that deletes email. This should fail.
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("Message");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema, true).status(), ProtoIsOk());
// 4. Try to delete by email type.
EXPECT_THAT(icing.DeleteBySchemaType("Email").status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
}
}
TEST_F(IcingSearchEngineTest, SetSchemaDuplicateTypesReturnsAlreadyExists) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// Create a schema with types { "Email", "Message" and "Email" }
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("Email");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("title");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
type = schema.add_types();
type->set_schema_type("Message");
property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
*schema.add_types() = schema.types(0);
EXPECT_THAT(icing.SetSchema(schema).status(),
ProtoStatusIs(StatusProto::ALREADY_EXISTS));
}
TEST_F(IcingSearchEngineTest,
SetSchemaDuplicatePropertiesReturnsAlreadyExists) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// Create a schema with an Email type with properties { "title", "body" and
// "title" }
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("Email");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("title");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property = type->add_properties();
property->set_property_name("title");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema).status(),
ProtoStatusIs(StatusProto::ALREADY_EXISTS));
}
TEST_F(IcingSearchEngineTest, SetSchema) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
auto message_document = CreateMessageDocument("namespace", "uri");
auto schema_with_message = CreateMessageSchema();
SchemaProto schema_with_email;
SchemaTypeConfigProto* type = schema_with_email.add_types();
type->set_schema_type("Email");
PropertyConfigProto* property = type->add_properties();
property->set_property_name("title");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
SchemaProto schema_with_email_and_message = schema_with_email;
type = schema_with_email_and_message.add_types();
type->set_schema_type("Message");
property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
// Create an arbitrary invalid schema
SchemaProto invalid_schema;
SchemaTypeConfigProto* empty_type = invalid_schema.add_types();
empty_type->set_schema_type("");
// Make sure we can't set invalid schemas
EXPECT_THAT(icing.SetSchema(invalid_schema).status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
// Can add an document of a set schema
EXPECT_THAT(icing.SetSchema(schema_with_message).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(message_document).status(), ProtoIsOk());
// Schema with Email doesn't have Message, so would result incompatible
// data
EXPECT_THAT(icing.SetSchema(schema_with_email).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
// Can expand the set of schema types and add an document of a new
// schema type
EXPECT_THAT(icing.SetSchema(SchemaProto(schema_with_email_and_message))
.status()
.code(),
Eq(StatusProto::OK));
EXPECT_THAT(icing.Put(message_document).status(), ProtoIsOk());
// Can't add an document whose schema isn't set
auto photo_document = DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Photo")
.AddStringProperty("creator", "icing")
.Build();
PutResultProto put_result_proto = icing.Put(photo_document);
EXPECT_THAT(put_result_proto.status(), ProtoStatusIs(StatusProto::NOT_FOUND));
EXPECT_THAT(put_result_proto.status().message(),
HasSubstr("'Photo' not found"));
}
TEST_F(IcingSearchEngineTest, SetSchemaTriggersIndexRestorationAndReturnsOk) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema_with_no_indexed_property = CreateMessageSchema();
schema_with_no_indexed_property.mutable_types(0)
->mutable_properties(0)
->clear_string_indexing_config();
EXPECT_THAT(icing.SetSchema(schema_with_no_indexed_property).status(),
ProtoIsOk());
// Nothing will be index and Search() won't return anything.
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto empty_result;
empty_result.mutable_status()->set_code(StatusProto::OK);
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results, EqualsSearchResultIgnoreStats(empty_result));
SchemaProto schema_with_indexed_property = CreateMessageSchema();
// Index restoration should be triggered here because new schema requires more
// properties to be indexed.
EXPECT_THAT(icing.SetSchema(schema_with_indexed_property).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() =
CreateMessageDocument("namespace", "uri");
actual_results = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SetSchemaRevalidatesDocumentsAndReturnsOk) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema_with_optional_subject;
auto type = schema_with_optional_subject.add_types();
type->set_schema_type("email");
// Add a OPTIONAL property
auto property = type->add_properties();
property->set_property_name("subject");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema_with_optional_subject).status(),
ProtoIsOk());
DocumentProto email_document_without_subject =
DocumentBuilder()
.SetKey("namespace", "without_subject")
.SetSchema("email")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto email_document_with_subject =
DocumentBuilder()
.SetKey("namespace", "with_subject")
.SetSchema("email")
.AddStringProperty("subject", "foo")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
EXPECT_THAT(icing.Put(email_document_without_subject).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(email_document_with_subject).status(), ProtoIsOk());
SchemaProto schema_with_required_subject;
type = schema_with_required_subject.add_types();
type->set_schema_type("email");
// Add a REQUIRED property
property = type->add_properties();
property->set_property_name("subject");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
// Can't set the schema since it's incompatible
SetSchemaResultProto expected_set_schema_result_proto;
expected_set_schema_result_proto.mutable_status()->set_code(
StatusProto::FAILED_PRECONDITION);
expected_set_schema_result_proto.mutable_status()->set_message(
"Schema is incompatible.");
expected_set_schema_result_proto.add_incompatible_schema_types("email");
EXPECT_THAT(icing.SetSchema(schema_with_required_subject),
EqualsProto(expected_set_schema_result_proto));
// Force set it
expected_set_schema_result_proto.mutable_status()->set_code(StatusProto::OK);
expected_set_schema_result_proto.mutable_status()->clear_message();
EXPECT_THAT(icing.SetSchema(schema_with_required_subject,
/*ignore_errors_and_delete_documents=*/true),
EqualsProto(expected_set_schema_result_proto));
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = email_document_with_subject;
EXPECT_THAT(icing.Get("namespace", "with_subject"),
EqualsProto(expected_get_result_proto));
// The document without a subject got deleted because it failed validation
// against the new schema
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace, without_subject) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace", "without_subject"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, SetSchemaDeletesDocumentsAndReturnsOk) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("email");
type = schema.add_types();
type->set_schema_type("message");
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto email_document =
DocumentBuilder()
.SetKey("namespace", "email_uri")
.SetSchema("email")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto message_document =
DocumentBuilder()
.SetKey("namespace", "message_uri")
.SetSchema("message")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
EXPECT_THAT(icing.Put(email_document).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(message_document).status(), ProtoIsOk());
// Clear the schema and only add the "email" type, essentially deleting the
// "message" type
SchemaProto new_schema;
type = new_schema.add_types();
type->set_schema_type("email");
// Can't set the schema since it's incompatible
SetSchemaResultProto expected_result;
expected_result.mutable_status()->set_code(StatusProto::FAILED_PRECONDITION);
expected_result.mutable_status()->set_message("Schema is incompatible.");
expected_result.add_deleted_schema_types("message");
EXPECT_THAT(icing.SetSchema(new_schema), EqualsProto(expected_result));
// Force set it
expected_result.mutable_status()->set_code(StatusProto::OK);
expected_result.mutable_status()->clear_message();
EXPECT_THAT(icing.SetSchema(new_schema,
/*ignore_errors_and_delete_documents=*/true),
EqualsProto(expected_result));
// "email" document is still there
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = email_document;
EXPECT_THAT(icing.Get("namespace", "email_uri"),
EqualsProto(expected_get_result_proto));
// "message" document got deleted
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace, message_uri) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace", "message_uri"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, GetSchemaNotFound) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.GetSchema().status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
}
TEST_F(IcingSearchEngineTest, GetSchemaOk) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
GetSchemaResultProto expected_get_schema_result_proto;
expected_get_schema_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_schema_result_proto.mutable_schema() = CreateMessageSchema();
EXPECT_THAT(icing.GetSchema(), EqualsProto(expected_get_schema_result_proto));
}
TEST_F(IcingSearchEngineTest, GetSchemaTypeFailedPrecondition) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
GetSchemaTypeResultProto get_schema_type_result_proto =
icing.GetSchemaType("nonexistent_schema");
EXPECT_THAT(get_schema_type_result_proto.status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(get_schema_type_result_proto.status().message(),
HasSubstr("Schema not set"));
}
TEST_F(IcingSearchEngineTest, GetSchemaTypeOk) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
GetSchemaTypeResultProto expected_get_schema_type_result_proto;
expected_get_schema_type_result_proto.mutable_status()->set_code(
StatusProto::OK);
*expected_get_schema_type_result_proto.mutable_schema_type_config() =
CreateMessageSchema().types(0);
EXPECT_THAT(icing.GetSchemaType(CreateMessageSchema().types(0).schema_type()),
EqualsProto(expected_get_schema_type_result_proto));
}
TEST_F(IcingSearchEngineTest, GetDocument) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Simple put and get
ASSERT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
ASSERT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
// Put an invalid document
PutResultProto put_result_proto = icing.Put(DocumentProto());
EXPECT_THAT(put_result_proto.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
EXPECT_THAT(put_result_proto.status().message(),
HasSubstr("'namespace' is empty"));
// Get a non-existing key
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (wrong, uri) not found.");
expected_get_result_proto.clear_document();
ASSERT_THAT(icing.Get("wrong", "uri"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchReturnsValidResults) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document_one = CreateMessageDocument("namespace", "uri1");
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two = CreateMessageDocument("namespace", "uri2");
ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("message");
ResultSpecProto result_spec;
result_spec.mutable_snippet_spec()->set_max_window_bytes(64);
result_spec.mutable_snippet_spec()->set_num_matches_per_property(1);
result_spec.mutable_snippet_spec()->set_num_to_snippet(1);
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.results(), SizeIs(2));
EXPECT_THAT(results.results(0).document(), EqualsProto(document_two));
EXPECT_THAT(GetMatch(results.results(0).document(),
results.results(0).snippet(), "body",
/*snippet_index=*/0),
Eq("message"));
EXPECT_THAT(
GetWindow(results.results(0).document(), results.results(0).snippet(),
"body", /*snippet_index=*/0),
Eq("message body"));
EXPECT_THAT(results.results(1).document(), EqualsProto(document_one));
EXPECT_THAT(
GetMatch(results.results(1).document(), results.results(1).snippet(),
"body", /*snippet_index=*/0),
IsEmpty());
EXPECT_THAT(
GetWindow(results.results(1).document(), results.results(1).snippet(),
"body", /*snippet_index=*/0),
IsEmpty());
search_spec.set_query("foo");
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchReturnsOneResult) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document_one = CreateMessageDocument("namespace", "uri1");
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two = CreateMessageDocument("namespace", "uri2");
ASSERT_THAT(icing.Put(document_two).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);
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_two;
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(search_result_proto.status(), ProtoIsOk());
// The token is a random number so we don't verify it.
expected_search_result_proto.set_next_page_token(
search_result_proto.next_page_token());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchZeroResultLimitReturnsEmptyResults) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("");
ResultSpecProto result_spec;
result_spec.set_num_per_page(0);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchNegativeResultLimitReturnsInvalidArgument) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("");
ResultSpecProto result_spec;
result_spec.set_num_per_page(-5);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(
StatusProto::INVALID_ARGUMENT);
expected_search_result_proto.mutable_status()->set_message(
"ResultSpecProto.num_per_page cannot be negative.");
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchWithPersistenceReturnsValidResults) {
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
{
// Set the schema up beforehand.
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Schema will be persisted to disk when icing goes out of scope.
}
{
// Ensure that icing initializes the schema and section_manager
// properly from the pre-existing file.
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
// The index and document store will be persisted to disk when icing goes
// out of scope.
}
{
// Ensure that the index is brought back up without problems and we
// can query for the content that we expect.
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("message");
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
SearchResultProto actual_results =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
search_spec.set_query("foo");
SearchResultProto empty_result;
empty_result.mutable_status()->set_code(StatusProto::OK);
actual_results = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(actual_results, EqualsSearchResultIgnoreStats(empty_result));
}
}
TEST_F(IcingSearchEngineTest, SearchShouldReturnEmpty) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("message");
// Empty result, no next-page token
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchShouldReturnMultiplePages) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates and inserts 5 documents
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
DocumentProto document3 = CreateMessageDocument("namespace", "uri3");
DocumentProto document4 = CreateMessageDocument("namespace", "uri4");
DocumentProto document5 = CreateMessageDocument("namespace", "uri5");
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document4).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document5).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(2);
// Searches and gets the first page, 2 results
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document5;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document4;
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,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Second page, 2 results
expected_search_result_proto.clear_results();
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
search_result_proto = icing.GetNextPage(next_page_token);
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Third page, 1 result
expected_search_result_proto.clear_results();
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
// Because there are no more results, we should not return the next page
// token.
expected_search_result_proto.clear_next_page_token();
search_result_proto = icing.GetNextPage(next_page_token);
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// No more results
expected_search_result_proto.clear_results();
search_result_proto = icing.GetNextPage(next_page_token);
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchWithNoScoringShouldReturnMultiplePages) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates and inserts 5 documents
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
DocumentProto document3 = CreateMessageDocument("namespace", "uri3");
DocumentProto document4 = CreateMessageDocument("namespace", "uri4");
DocumentProto document5 = CreateMessageDocument("namespace", "uri5");
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document4).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document5).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("message");
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::NONE);
ResultSpecProto result_spec;
result_spec.set_num_per_page(2);
// Searches and gets the first page, 2 results
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document5;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document4;
SearchResultProto search_result_proto =
icing.Search(search_spec, scoring_spec, 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,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Second page, 2 results
expected_search_result_proto.clear_results();
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
search_result_proto = icing.GetNextPage(next_page_token);
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Third page, 1 result
expected_search_result_proto.clear_results();
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
// Because there are no more results, we should not return the next page
// token.
expected_search_result_proto.clear_next_page_token();
search_result_proto = icing.GetNextPage(next_page_token);
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// No more results
expected_search_result_proto.clear_results();
search_result_proto = icing.GetNextPage(next_page_token);
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, ShouldReturnMultiplePagesWithSnippets) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates and inserts 5 documents
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
DocumentProto document3 = CreateMessageDocument("namespace", "uri3");
DocumentProto document4 = CreateMessageDocument("namespace", "uri4");
DocumentProto document5 = CreateMessageDocument("namespace", "uri5");
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document4).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document5).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(2);
result_spec.mutable_snippet_spec()->set_max_window_bytes(64);
result_spec.mutable_snippet_spec()->set_num_matches_per_property(1);
result_spec.mutable_snippet_spec()->set_num_to_snippet(3);
// Searches and gets the first page, 2 results with 2 snippets
SearchResultProto search_result =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
ASSERT_THAT(search_result.status(), ProtoIsOk());
ASSERT_THAT(search_result.results(), SizeIs(2));
ASSERT_THAT(search_result.next_page_token(), Gt(kInvalidNextPageToken));
EXPECT_THAT(search_result.results(0).document(), EqualsProto(document5));
EXPECT_THAT(GetMatch(search_result.results(0).document(),
search_result.results(0).snippet(), "body",
/*snippet_index=*/0),
Eq("message"));
EXPECT_THAT(GetWindow(search_result.results(0).document(),
search_result.results(0).snippet(), "body",
/*snippet_index=*/0),
Eq("message body"));
EXPECT_THAT(search_result.results(1).document(), EqualsProto(document4));
EXPECT_THAT(GetMatch(search_result.results(1).document(),
search_result.results(1).snippet(), "body",
/*snippet_index=*/0),
Eq("message"));
EXPECT_THAT(GetWindow(search_result.results(1).document(),
search_result.results(1).snippet(), "body",
/*snippet_index=*/0),
Eq("message body"));
// Second page, 2 result with 1 snippet
search_result = icing.GetNextPage(search_result.next_page_token());
ASSERT_THAT(search_result.status(), ProtoIsOk());
ASSERT_THAT(search_result.results(), SizeIs(2));
ASSERT_THAT(search_result.next_page_token(), Gt(kInvalidNextPageToken));
EXPECT_THAT(search_result.results(0).document(), EqualsProto(document3));
EXPECT_THAT(GetMatch(search_result.results(0).document(),
search_result.results(0).snippet(), "body",
/*snippet_index=*/0),
Eq("message"));
EXPECT_THAT(GetWindow(search_result.results(0).document(),
search_result.results(0).snippet(), "body",
/*snippet_index=*/0),
Eq("message body"));
EXPECT_THAT(search_result.results(1).document(), EqualsProto(document2));
EXPECT_THAT(search_result.results(1).snippet().entries_size(), Eq(0));
// Third page, 1 result with 0 snippets
search_result = icing.GetNextPage(search_result.next_page_token());
ASSERT_THAT(search_result.status(), ProtoIsOk());
ASSERT_THAT(search_result.results(), SizeIs(1));
ASSERT_THAT(search_result.next_page_token(), Eq(kInvalidNextPageToken));
EXPECT_THAT(search_result.results(0).document(), EqualsProto(document1));
EXPECT_THAT(search_result.results(0).snippet().entries_size(), Eq(0));
}
TEST_F(IcingSearchEngineTest, ShouldInvalidateNextPageToken) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
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,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Now document1 is still to be fetched.
// Invalidates token
icing.InvalidateNextPageToken(next_page_token);
// Tries to fetch the second page, no result since it's invalidated
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,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest,
AllPageTokensShouldBeInvalidatedAfterOptimization) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
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,
EqualsSearchResultIgnoreStats(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("");
ASSERT_THAT(icing.Optimize(), 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,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, OptimizationShouldRemoveDeletedDocs) {
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
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(CreateMessageSchema()).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/document_log";
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"),
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"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, OptimizationShouldDeleteTemporaryDirectory) {
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).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(IcingSearchEngineTest, GetOptimizeInfoHasCorrectStats) {
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.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));
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).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));
// 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));
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));
// Optimize
ASSERT_THAT(icing.Optimize().status(), ProtoIsOk());
// Nothing is optimizable now that everything has been optimized away.
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));
}
TEST_F(IcingSearchEngineTest, GetAndPutShouldWorkAfterOptimization) {
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
DocumentProto document3 = CreateMessageDocument("namespace", "uri3");
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Optimize().status(), ProtoIsOk());
// Validates that Get() and Put() are good right after Optimize()
EXPECT_THAT(icing.Get("namespace", "uri1"),
EqualsProto(expected_get_result_proto));
EXPECT_THAT(icing.Put(document2).status(), ProtoIsOk());
} // Destroys IcingSearchEngine to make sure nothing is cached.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace", "uri2"),
EqualsProto(expected_get_result_proto));
EXPECT_THAT(icing.Put(document3).status(), ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, DeleteShouldWorkAfterOptimization) {
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).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"),
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"),
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"),
EqualsProto(expected_get_result_proto));
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace, uri2) not found.");
EXPECT_THAT(icing.Get("namespace", "uri2"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, 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;
};
ON_CALL(*mock_filesystem, SwapFiles).WillByDefault(swap_lambda);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
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;
auto type = simple_schema.add_types();
type->set_schema_type("type0");
auto property = type->add_properties();
property->set_property_name("prop0");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
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()).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()).status(),
ProtoIsOk());
EXPECT_THAT(icing.Search(search_spec, scoring_spec, result_spec).status(),
ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, DeleteBySchemaType) {
SchemaProto schema;
// Add an email type
auto type = schema.add_types();
type->set_schema_type("email");
auto property = type->add_properties();
property->set_property_name("subject");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::EXACT_ONLY);
property->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
// Add an message type
type = schema.add_types();
type->set_schema_type("message");
property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::EXACT_ONLY);
property->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("message")
.AddStringProperty("body", "message body1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("email")
.AddStringProperty("subject", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(7);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
// Delete the first type. The first doc should be irretrievable. The
// second should still be present.
DeleteBySchemaTypeResultProto result_proto =
icing.DeleteBySchemaType("message");
EXPECT_THAT(result_proto.status(), ProtoIsOk());
NativeDeleteStats exp_stats;
exp_stats.set_delete_type(NativeDeleteStats::DeleteType::SCHEMA_TYPE);
exp_stats.set_latency_ms(7);
exp_stats.set_num_documents_deleted(1);
EXPECT_THAT(result_proto.delete_stats(), EqualsProto(exp_stats));
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace1, uri1) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace1", "uri1"),
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("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
// Search for "message", only document2 should show up.
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;
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
search_spec.set_query("message");
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, DeleteSchemaTypeByQuery) {
SchemaProto schema = CreateMessageSchema();
// Add an email type
SchemaProto tmp = CreateEmailSchema();
*schema.add_types() = tmp.types(0);
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema(schema.types(0).schema_type())
.AddStringProperty("body", "message body1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema(schema.types(1).schema_type())
.AddStringProperty("subject", "subject subject2")
.AddStringProperty("body", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document2).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
// Delete the first type. The first doc should be irretrievable. The
// second should still be present.
SearchSpecProto search_spec;
search_spec.add_schema_type_filters(schema.types(0).schema_type());
EXPECT_THAT(icing.DeleteByQuery(search_spec).status(), ProtoIsOk());
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace1, uri1) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace1", "uri1"),
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("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
search_spec = SearchSpecProto::default_instance();
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, DeleteByNamespace) {
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "message body1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace3", "uri3")
.SetSchema("Message")
.AddStringProperty("body", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(7);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace1", "uri2"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document3;
EXPECT_THAT(icing.Get("namespace3", "uri3"),
EqualsProto(expected_get_result_proto));
// Delete namespace1. Document1 and document2 should be irretrievable.
// Document3 should still be present.
DeleteByNamespaceResultProto result_proto =
icing.DeleteByNamespace("namespace1");
EXPECT_THAT(result_proto.status(), ProtoIsOk());
NativeDeleteStats exp_stats;
exp_stats.set_delete_type(NativeDeleteStats::DeleteType::NAMESPACE);
exp_stats.set_latency_ms(7);
exp_stats.set_num_documents_deleted(2);
EXPECT_THAT(result_proto.delete_stats(), EqualsProto(exp_stats));
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace1, uri1) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace1, uri2) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace1", "uri2"),
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() = document3;
EXPECT_THAT(icing.Get("namespace3", "uri3"),
EqualsProto(expected_get_result_proto));
// Search for "message", only document3 should show up.
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
search_spec.set_query("message");
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, DeleteNamespaceByQuery) {
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "message body1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document2).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
// Delete the first namespace. The first doc should be irretrievable. The
// second should still be present.
SearchSpecProto search_spec;
search_spec.add_namespace_filters("namespace1");
EXPECT_THAT(icing.DeleteByQuery(search_spec).status(), ProtoIsOk());
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace1, uri1) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace1", "uri1"),
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("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
search_spec = SearchSpecProto::default_instance();
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, DeleteByQuery) {
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "message body1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(7);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document2).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
// Delete all docs containing 'body1'. The first doc should be irretrievable.
// The second should still be present.
SearchSpecProto search_spec;
search_spec.set_query("body1");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
DeleteByQueryResultProto result_proto = icing.DeleteByQuery(search_spec);
EXPECT_THAT(result_proto.status(), ProtoIsOk());
NativeDeleteStats exp_stats;
exp_stats.set_delete_type(NativeDeleteStats::DeleteType::QUERY);
exp_stats.set_latency_ms(7);
exp_stats.set_num_documents_deleted(1);
EXPECT_THAT(result_proto.delete_stats(), EqualsProto(exp_stats));
expected_get_result_proto.mutable_status()->set_code(StatusProto::NOT_FOUND);
expected_get_result_proto.mutable_status()->set_message(
"Document (namespace1, uri1) not found.");
expected_get_result_proto.clear_document();
EXPECT_THAT(icing.Get("namespace1", "uri1"),
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("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
search_spec = SearchSpecProto::default_instance();
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, DeleteByQueryNotFound) {
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "message body1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "message body2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document2).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
// Delete all docs containing 'foo', which should be none of them. Both docs
// should still be present.
SearchSpecProto search_spec;
search_spec.set_query("foo");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
EXPECT_THAT(icing.DeleteByQuery(search_spec).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
expected_get_result_proto.mutable_status()->clear_message();
*expected_get_result_proto.mutable_document() = document1;
EXPECT_THAT(icing.Get("namespace1", "uri1"),
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("namespace2", "uri2"),
EqualsProto(expected_get_result_proto));
search_spec = SearchSpecProto::default_instance();
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SetSchemaShouldWorkAfterOptimization) {
// Creates 3 test schemas
SchemaProto schema1 = SchemaProto(CreateMessageSchema());
SchemaProto schema2 = SchemaProto(schema1);
auto new_property2 = schema2.mutable_types(0)->add_properties();
new_property2->set_property_name("property2");
new_property2->set_data_type(PropertyConfigProto::DataType::STRING);
new_property2->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
new_property2->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
new_property2->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
SchemaProto schema3 = SchemaProto(schema2);
auto new_property3 = schema3.mutable_types(0)->add_properties();
new_property3->set_property_name("property3");
new_property3->set_data_type(PropertyConfigProto::DataType::STRING);
new_property3->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
new_property3->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
new_property3->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
{
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(IcingSearchEngineTest, SearchShouldWorkAfterOptimization) {
DocumentProto document = CreateMessageDocument("namespace", "uri");
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
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(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
ASSERT_THAT(icing.Optimize().status(), ProtoIsOk());
// Validates that Search() works right after Optimize()
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(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(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, IcingShouldWorkFineIfOptimizationIsAborted) {
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
{
// Initializes a normal icing to create files needed
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).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)
.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() = document1;
EXPECT_THAT(icing.Get("namespace", "uri1"),
EqualsProto(expected_get_result_proto));
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
EXPECT_THAT(icing.Put(document2).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("m");
search_spec.set_term_match_type(TermMatchType::PREFIX);
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;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest,
OptimizationShouldRecoverIfFileDirectoriesAreMissing) {
// 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)
.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(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
// Optimize() fails due to filesystem error
EXPECT_THAT(icing.Optimize().status(),
ProtoStatusIs(StatusProto::WARNING_DATA_LOSS));
// 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"),
EqualsProto(expected_get_result_proto));
DocumentProto new_document =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "new body")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
EXPECT_THAT(icing.Put(new_document).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("m");
search_spec.set_term_match_type(TermMatchType::PREFIX);
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
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
search_spec.set_query("n");
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
new_document;
// Searching new content returns the new document
search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, OptimizationShouldRecoverIfDataFilesAreMissing) {
// 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)
.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(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
// Optimize() fails due to filesystem error
EXPECT_THAT(icing.Optimize().status(),
ProtoStatusIs(StatusProto::WARNING_DATA_LOSS));
// 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"),
EqualsProto(expected_get_result_proto));
DocumentProto new_document =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "new body")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
EXPECT_THAT(icing.Put(new_document).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("m");
search_spec.set_term_match_type(TermMatchType::PREFIX);
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
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
search_spec.set_query("n");
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
new_document;
// Searching new content returns the new document
search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchIncludesDocumentsBeforeTtl) {
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("Message");
auto body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
DocumentProto document = DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(100)
.SetTtlMs(500)
.Build();
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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;
// Time just has to be less than the document's creation timestamp (100) + the
// document's ttl (500)
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(400);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// Check that the document is returned as part of search results
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchDoesntIncludeDocumentsPastTtl) {
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("Message");
auto body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
DocumentProto document = DocumentBuilder()
.SetKey("namespace", "uri")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(100)
.SetTtlMs(500)
.Build();
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
// Time just has to be greater than the document's creation timestamp (100) +
// the document's ttl (500)
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(700);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// Check that the document is not returned as part of search results
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchWorksAfterSchemaTypesCompatiblyModified) {
SchemaProto schema;
auto type_config = schema.add_types();
type_config->set_schema_type("message");
auto property = type_config->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
DocumentProto message_document =
DocumentBuilder()
.SetKey("namespace", "message_uri")
.SetSchema("message")
.AddStringProperty("body", "foo")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(message_document).status(), ProtoIsOk());
// Make sure we can search for message document
SearchSpecProto search_spec;
search_spec.set_query("foo");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
// The message isn't indexed, so we get nothing
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// With just the schema type filter, we can search for the message
search_spec.Clear();
search_spec.add_schema_type_filters("message");
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
message_document;
search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Since SchemaTypeIds are assigned based on order in the SchemaProto, this
// will force a change in the DocumentStore's cached SchemaTypeIds
schema.clear_types();
type_config = schema.add_types();
type_config->set_schema_type("email");
// Adding a new indexed property will require reindexing
type_config = schema.add_types();
type_config->set_schema_type("message");
property = type_config->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
property->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
search_spec.Clear();
search_spec.set_query("foo");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
search_spec.add_schema_type_filters("message");
// We can still search for the message document
search_result_proto = icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, RecoverFromMissingHeaderFile) {
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
{
// Basic initialization/setup
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
EXPECT_TRUE(filesystem()->DeleteFile(GetHeaderFilename().c_str()));
// We should be able to recover from this and access all our previous data
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
// Checks that DocumentLog is still ok
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
// Checks that the index is still ok so we can search over it
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Checks that Schema is still since it'll be needed to validate the document
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, RecoverFromInvalidHeaderMagic) {
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
{
// Basic initialization/setup
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
// Change the header's magic value
int32_t invalid_magic = 1; // Anything that's not the actual kMagic value.
filesystem()->PWrite(GetHeaderFilename().c_str(),
offsetof(IcingSearchEngine::Header, magic),
&invalid_magic, sizeof(invalid_magic));
// We should be able to recover from this and access all our previous data
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
// Checks that DocumentLog is still ok
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
// Checks that the index is still ok so we can search over it
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Checks that Schema is still since it'll be needed to validate the document
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, RecoverFromInvalidHeaderChecksum) {
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
{
// Basic initialization/setup
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
// Change the header's checksum value
uint32_t invalid_checksum =
1; // Anything that's not the actual checksum value
filesystem()->PWrite(GetHeaderFilename().c_str(),
offsetof(IcingSearchEngine::Header, checksum),
&invalid_checksum, sizeof(invalid_checksum));
// We should be able to recover from this and access all our previous data
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
// Checks that DocumentLog is still ok
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
// Checks that the index is still ok so we can search over it
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
// Checks that Schema is still since it'll be needed to validate the document
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, UnableToRecoverFromCorruptSchema) {
{
// Basic initialization/setup
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
const std::string schema_file =
absl_ports::StrCat(GetSchemaDir(), "/schema.pb");
const std::string corrupt_data = "1234";
EXPECT_TRUE(filesystem()->Write(schema_file.c_str(), corrupt_data.data(),
corrupt_data.size()));
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INTERNAL));
}
TEST_F(IcingSearchEngineTest, UnableToRecoverFromCorruptDocumentLog) {
{
// Basic initialization/setup
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
const std::string document_log_file =
absl_ports::StrCat(GetDocumentDir(), "/document_log");
const std::string corrupt_data = "1234";
EXPECT_TRUE(filesystem()->Write(document_log_file.c_str(),
corrupt_data.data(), corrupt_data.size()));
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::INTERNAL));
}
TEST_F(IcingSearchEngineTest, RecoverFromInconsistentSchemaStore) {
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2_with_additional_property =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Message")
.AddStringProperty("additional", "content")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
{
// Initializes folder and schema
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("Message");
auto property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
property->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
property->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
property = type->add_properties();
property->set_property_name("additional");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
EXPECT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document2_with_additional_property).status(),
ProtoIsOk());
// Won't get us anything because "additional" isn't marked as an indexed
// property in the schema
SearchSpecProto search_spec;
search_spec.set_query("additional:content");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
{
// This schema will change the SchemaTypeIds from the previous schema_
// (since SchemaTypeIds are assigned based on order of the types, and this
// new schema changes the ordering of previous types)
SchemaProto new_schema;
auto type = new_schema.add_types();
type->set_schema_type("Email");
type = new_schema.add_types();
type->set_schema_type("Message");
// Adding a new property changes the SectionIds (since SectionIds are
// assigned based on alphabetical order of indexed sections, marking
// "additional" as an indexed property will push the "body" property to a
// different SectionId)
auto property = type->add_properties();
property->set_property_name("body");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
property->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
property->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
property = type->add_properties();
property->set_property_name("additional");
property->set_data_type(PropertyConfigProto::DataType::STRING);
property->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
property->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
property->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
FakeClock fake_clock;
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<SchemaStore> schema_store,
SchemaStore::Create(filesystem(), GetSchemaDir(), &fake_clock));
ICING_EXPECT_OK(schema_store->SetSchema(new_schema));
} // Will persist new schema
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
// We can insert a Email document since we kept the new schema
DocumentProto email_document =
DocumentBuilder()
.SetKey("namespace", "email_uri")
.SetSchema("Email")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
EXPECT_THAT(icing.Put(email_document).status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = email_document;
EXPECT_THAT(icing.Get("namespace", "email_uri"),
EqualsProto(expected_get_result_proto));
SearchSpecProto search_spec;
// The section restrict will ensure we are using the correct, updated
// SectionId in the Index
search_spec.set_query("additional:content");
// Schema type filter will ensure we're using the correct, updated
// SchemaTypeId in the DocumentStore
search_spec.add_schema_type_filters("Message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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_with_additional_property;
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, RecoverFromInconsistentDocumentStore) {
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
{
// Initializes folder and schema, index one document
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
} // This should shut down IcingSearchEngine and persist anything it needs to
{
FakeClock fake_clock;
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<SchemaStore> schema_store,
SchemaStore::Create(filesystem(), GetSchemaDir(), &fake_clock));
ICING_EXPECT_OK(schema_store->SetSchema(CreateMessageSchema()));
// Puts a second document into DocumentStore but doesn't index it.
ICING_ASSERT_OK_AND_ASSIGN(
DocumentStore::CreateResult create_result,
DocumentStore::Create(filesystem(), GetDocumentDir(), &fake_clock,
schema_store.get()));
std::unique_ptr<DocumentStore> document_store =
std::move(create_result.document_store);
ICING_EXPECT_OK(document_store->Put(document2));
}
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
// Index Restoration should be triggered here and document2 should be
// indexed.
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document1;
// DocumentStore kept the additional document
EXPECT_THAT(icing.Get("namespace", "uri1"),
EqualsProto(expected_get_result_proto));
*expected_get_result_proto.mutable_document() = document2;
EXPECT_THAT(icing.Get("namespace", "uri2"),
EqualsProto(expected_get_result_proto));
// We indexed the additional document
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
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;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, RecoverFromInconsistentIndex) {
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
{
// Initializes folder and schema, index one document
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
// Pretend we lost the entire index
EXPECT_TRUE(filesystem()->DeleteDirectoryRecursively(
absl_ports::StrCat(GetIndexDir(), "/idx/lite.").c_str()));
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
// Check that our index is ok by searching over the restored index
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, RecoverFromCorruptIndex) {
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
{
// Initializes folder and schema, index one document
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
// Pretend index is corrupted
const std::string index_hit_buffer_file = GetIndexDir() + "/idx/lite.hb";
ScopedFd fd(filesystem()->OpenForWrite(index_hit_buffer_file.c_str()));
ASSERT_TRUE(fd.is_valid());
ASSERT_TRUE(filesystem()->Write(fd.get(), "1234", 4));
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
// Check that our index is ok by searching over the restored index
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchResultShouldBeRankedByDocumentScore) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 documents and ensures the relationship in terms of document
// score is: document1 < document2 < document3
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetScore(1)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetScore(2)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetScore(3)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
// Intentionally inserts the documents in the order that is different than
// their score order
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// Result should be in descending score order
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
ScoringSpecProto scoring_spec = GetDefaultScoringSpec();
scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchShouldAllowNoScoring) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 documents and ensures the relationship of them is:
// document1 < document2 < document3
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetScore(1)
.SetCreationTimestampMs(1571111111111)
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetScore(2)
.SetCreationTimestampMs(1572222222222)
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetScore(3)
.SetCreationTimestampMs(1573333333333)
.Build();
// Intentionally inserts the documents in the order that is different than
// their score order
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
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;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
// Results should not be ranked by score but returned in reverse insertion
// order.
ScoringSpecProto scoring_spec = GetDefaultScoringSpec();
scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::NONE);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchResultShouldBeRankedByCreationTimestamp) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 documents and ensures the relationship in terms of creation
// timestamp score is: document1 < document2 < document3
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetCreationTimestampMs(1571111111111)
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetCreationTimestampMs(1572222222222)
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetCreationTimestampMs(1573333333333)
.Build();
// Intentionally inserts the documents in the order that is different than
// their score order
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// Result should be in descending timestamp order
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
ScoringSpecProto scoring_spec = GetDefaultScoringSpec();
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::CREATION_TIMESTAMP);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchResultShouldBeRankedByUsageCount) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 test documents
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
// Intentionally inserts the documents in a different order to eliminate the
// possibility that the following results are sorted in the default reverse
// insertion order.
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// Report usage for doc3 twice and doc2 once. The order will be doc3 > doc2 >
// doc1 when ranked by USAGE_TYPE1_COUNT.
UsageReport usage_report_doc3 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/3", /*timestamp_ms=*/0,
UsageReport::USAGE_TYPE1);
UsageReport usage_report_doc2 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/2", /*timestamp_ms=*/0,
UsageReport::USAGE_TYPE1);
ASSERT_THAT(icing.ReportUsage(usage_report_doc3).status(), ProtoIsOk());
ASSERT_THAT(icing.ReportUsage(usage_report_doc3).status(), ProtoIsOk());
ASSERT_THAT(icing.ReportUsage(usage_report_doc2).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// Result should be in descending USAGE_TYPE1_COUNT order
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::USAGE_TYPE1_COUNT);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest,
SearchResultShouldHaveDefaultOrderWithoutUsageCounts) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 test documents
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// None of the documents have usage reports. Result should be in the default
// reverse insertion order.
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::USAGE_TYPE1_COUNT);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchResultShouldBeRankedByUsageTimestamp) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 test documents
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
// Intentionally inserts the documents in a different order to eliminate the
// possibility that the following results are sorted in the default reverse
// insertion order.
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// Report usage for doc2 and doc3. The order will be doc3 > doc2 > doc1 when
// ranked by USAGE_TYPE1_LAST_USED_TIMESTAMP.
UsageReport usage_report_doc2 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/2", /*timestamp_ms=*/1000,
UsageReport::USAGE_TYPE1);
UsageReport usage_report_doc3 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/3", /*timestamp_ms=*/5000,
UsageReport::USAGE_TYPE1);
ASSERT_THAT(icing.ReportUsage(usage_report_doc2).status(), ProtoIsOk());
ASSERT_THAT(icing.ReportUsage(usage_report_doc3).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// Result should be in descending USAGE_TYPE1_LAST_USED_TIMESTAMP order
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::USAGE_TYPE1_LAST_USED_TIMESTAMP);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest,
SearchResultShouldHaveDefaultOrderWithoutUsageTimestamp) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 test documents
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// None of the documents have usage reports. Result should be in the default
// reverse insertion order.
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::USAGE_TYPE1_LAST_USED_TIMESTAMP);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, OlderUsageTimestampShouldNotOverrideNewerOnes) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 test documents
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// Report usage for doc1 and doc2. The older timestamp 5000 shouldn't be
// overridden by 1000. The order will be doc1 > doc2 when ranked by
// USAGE_TYPE1_LAST_USED_TIMESTAMP.
UsageReport usage_report_doc1_time1 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/1", /*timestamp_ms=*/1000,
UsageReport::USAGE_TYPE1);
UsageReport usage_report_doc1_time5 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/1", /*timestamp_ms=*/5000,
UsageReport::USAGE_TYPE1);
UsageReport usage_report_doc2_time3 = CreateUsageReport(
/*name_space=*/"namespace", /*uri=*/"uri/2", /*timestamp_ms=*/3000,
UsageReport::USAGE_TYPE1);
ASSERT_THAT(icing.ReportUsage(usage_report_doc1_time5).status(), ProtoIsOk());
ASSERT_THAT(icing.ReportUsage(usage_report_doc2_time3).status(), ProtoIsOk());
ASSERT_THAT(icing.ReportUsage(usage_report_doc1_time1).status(), ProtoIsOk());
// "m" will match both documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// Result should be in descending USAGE_TYPE1_LAST_USED_TIMESTAMP order
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::USAGE_TYPE1_LAST_USED_TIMESTAMP);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest, SearchResultShouldBeRankedAscendingly) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates 3 documents and ensures the relationship in terms of document
// score is: document1 < document2 < document3
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri/1")
.SetSchema("Message")
.AddStringProperty("body", "message1")
.SetScore(1)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri/2")
.SetSchema("Message")
.AddStringProperty("body", "message2")
.SetScore(2)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri/3")
.SetSchema("Message")
.AddStringProperty("body", "message3")
.SetScore(3)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
// Intentionally inserts the documents in the order that is different than
// their score order
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
// "m" will match all 3 documents
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("m");
// Result should be in ascending score order
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document1;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document2;
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
document3;
ScoringSpecProto scoring_spec = GetDefaultScoringSpec();
scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE);
scoring_spec.set_order_by(ScoringSpecProto::Order::ASC);
SearchResultProto search_result_proto = icing.Search(
search_spec, scoring_spec, ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
}
TEST_F(IcingSearchEngineTest,
SetSchemaCanNotDetectPreviousSchemaWasLostWithoutDocuments) {
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("Message");
auto body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
// Make an incompatible schema, a previously OPTIONAL field is REQUIRED
SchemaProto incompatible_schema = schema;
incompatible_schema.mutable_types(0)->mutable_properties(0)->set_cardinality(
PropertyConfigProto::Cardinality::REQUIRED);
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
} // This should shut down IcingSearchEngine and persist anything it needs to
ASSERT_TRUE(filesystem()->DeleteDirectoryRecursively(GetSchemaDir().c_str()));
// Since we don't have any documents yet, we can't detect this edge-case. But
// it should be fine since there aren't any documents to be invalidated.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(incompatible_schema).status(), ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, SetSchemaCanDetectPreviousSchemaWasLost) {
SchemaProto schema;
auto type = schema.add_types();
type->set_schema_type("Message");
auto body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::OPTIONAL);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
// Make an incompatible schema, a previously OPTIONAL field is REQUIRED
SchemaProto incompatible_schema = schema;
incompatible_schema.mutable_types(0)->mutable_properties(0)->set_cardinality(
PropertyConfigProto::Cardinality::REQUIRED);
SearchSpecProto search_spec;
search_spec.set_query("message");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document = CreateMessageDocument("namespace", "uri");
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
// Can retrieve by namespace/uri
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document;
ASSERT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
// Can search for it
SearchResultProto expected_search_result_proto;
expected_search_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_search_result_proto.mutable_results()->Add()->mutable_document() =
CreateMessageDocument("namespace", "uri");
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto,
EqualsSearchResultIgnoreStats(expected_search_result_proto));
} // This should shut down IcingSearchEngine and persist anything it needs to
ASSERT_TRUE(filesystem()->DeleteDirectoryRecursively(GetSchemaDir().c_str()));
// Setting the new, different schema will remove incompatible documents
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(incompatible_schema).status(), ProtoIsOk());
// Can't retrieve by namespace/uri
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"),
EqualsProto(expected_get_result_proto));
// Can't search for it
SearchResultProto empty_result;
empty_result.mutable_status()->set_code(StatusProto::OK);
SearchResultProto search_result_proto =
icing.Search(search_spec, GetDefaultScoringSpec(),
ResultSpecProto::default_instance());
EXPECT_THAT(search_result_proto, EqualsSearchResultIgnoreStats(empty_result));
}
TEST_F(IcingSearchEngineTest, PersistToDisk) {
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() =
CreateMessageDocument("namespace", "uri");
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
// Persisting shouldn't affect anything
EXPECT_THAT(icing.PersistToDisk().status(), ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
} // Destructing persists as well
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing.Initialize().status(), ProtoIsOk());
EXPECT_THAT(icing.Get("namespace", "uri"),
EqualsProto(expected_get_result_proto));
}
TEST_F(IcingSearchEngineTest, ResetOk) {
SchemaProto message_schema = CreateMessageSchema();
SchemaProto empty_schema = SchemaProto(message_schema);
empty_schema.clear_types();
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(message_schema).status(), ProtoIsOk());
int64_t empty_state_size =
filesystem()->GetFileDiskUsage(GetTestBaseDir().c_str());
DocumentProto document = CreateMessageDocument("namespace", "uri");
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
// Check that things have been added
EXPECT_THAT(filesystem()->GetDiskUsage(GetTestBaseDir().c_str()),
Gt(empty_state_size));
EXPECT_THAT(icing.Reset().status(), ProtoIsOk());
// Check that we're back to an empty state
EXPECT_EQ(filesystem()->GetFileDiskUsage(GetTestBaseDir().c_str()),
empty_state_size);
// Sanity check that we can still call other APIs. If things aren't cleared,
// then this should raise an error since the empty schema is incompatible with
// the old message_schema.
EXPECT_THAT(icing.SetSchema(empty_schema).status(), ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, ResetAbortedError) {
auto mock_filesystem = std::make_unique<MockFilesystem>();
// This fails IcingSearchEngine::Reset(). But since we didn't actually delete
// anything, we'll be able to consider this just an ABORTED call.
ON_CALL(*mock_filesystem,
DeleteDirectoryRecursively(StrEq(GetTestBaseDir().c_str())))
.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());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document = CreateMessageDocument("namespace", "uri");
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
EXPECT_THAT(icing.Reset().status(), ProtoStatusIs(StatusProto::ABORTED));
// Everything is still intact.
// Can get old data.
GetResultProto expected_get_result_proto;
expected_get_result_proto.mutable_status()->set_code(StatusProto::OK);
*expected_get_result_proto.mutable_document() = document;
EXPECT_THAT(icing.Get(document.namespace_(), document.uri()),
EqualsProto(expected_get_result_proto));
// Can add new data.
EXPECT_THAT(icing.Put(CreateMessageDocument("namespace", "uri")).status(),
ProtoIsOk());
}
TEST_F(IcingSearchEngineTest, ResetInternalError) {
auto mock_filesystem = std::make_unique<MockFilesystem>();
// Let all other calls succeed.
EXPECT_CALL(*mock_filesystem, Write(Matcher<const char*>(_), _, _))
.WillRepeatedly(Return(true));
// This prevents IcingSearchEngine from creating a DocumentStore instance on
// reinitialization
const std::string document_log_path =
GetTestBaseDir() + "/document_dir/document_log";
EXPECT_CALL(
*mock_filesystem,
Write(Matcher<const char*>(StrEq(document_log_path.c_str())), _, _))
.WillOnce(Return(true))
.WillOnce(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(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Reset().status(), ProtoStatusIs(StatusProto::INTERNAL));
}
TEST_F(IcingSearchEngineTest, SnippetNormalization) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "MDI zurich Team Meeting")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "mdi Zürich Team Meeting")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
search_spec.set_query("mdi Zürich");
ResultSpecProto result_spec;
result_spec.mutable_snippet_spec()->set_max_window_bytes(64);
result_spec.mutable_snippet_spec()->set_num_matches_per_property(2);
result_spec.mutable_snippet_spec()->set_num_to_snippet(2);
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
ASSERT_THAT(results.results(), SizeIs(2));
const DocumentProto& result_document_1 = results.results(0).document();
const SnippetProto& result_snippet_1 = results.results(0).snippet();
EXPECT_THAT(result_document_1, EqualsProto(document_two));
EXPECT_THAT(GetMatch(result_document_1, result_snippet_1, "body",
/*snippet_index=*/0),
Eq("mdi"));
EXPECT_THAT(GetWindow(result_document_1, result_snippet_1, "body",
/*snippet_index=*/0),
Eq("mdi Zürich Team Meeting"));
EXPECT_THAT(GetMatch(result_document_1, result_snippet_1, "body",
/*snippet_index=*/1),
Eq("Zürich"));
EXPECT_THAT(GetWindow(result_document_1, result_snippet_1, "body",
/*snippet_index=*/1),
Eq("mdi Zürich Team Meeting"));
const DocumentProto& result_document_2 = results.results(1).document();
const SnippetProto& result_snippet_2 = results.results(1).snippet();
EXPECT_THAT(result_document_2, EqualsProto(document_one));
EXPECT_THAT(GetMatch(result_document_2, result_snippet_2, "body",
/*snippet_index=*/0),
Eq("MDI"));
EXPECT_THAT(GetWindow(result_document_2, result_snippet_2, "body",
/*snippet_index=*/0),
Eq("MDI zurich Team Meeting"));
EXPECT_THAT(GetMatch(result_document_2, result_snippet_2, "body",
/*snippet_index=*/1),
Eq("zurich"));
EXPECT_THAT(GetWindow(result_document_2, result_snippet_2, "body",
/*snippet_index=*/1),
Eq("MDI zurich Team Meeting"));
}
TEST_F(IcingSearchEngineTest, SnippetNormalizationPrefix) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "MDI zurich Team Meeting")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "mdi Zürich Team Meeting")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("md Zür");
ResultSpecProto result_spec;
result_spec.mutable_snippet_spec()->set_max_window_bytes(64);
result_spec.mutable_snippet_spec()->set_num_matches_per_property(2);
result_spec.mutable_snippet_spec()->set_num_to_snippet(2);
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
ASSERT_THAT(results.results(), SizeIs(2));
const DocumentProto& result_document_1 = results.results(0).document();
const SnippetProto& result_snippet_1 = results.results(0).snippet();
EXPECT_THAT(result_document_1, EqualsProto(document_two));
EXPECT_THAT(GetMatch(result_document_1, result_snippet_1, "body",
/*snippet_index=*/0),
Eq("mdi"));
EXPECT_THAT(GetWindow(result_document_1, result_snippet_1, "body",
/*snippet_index=*/0),
Eq("mdi Zürich Team Meeting"));
EXPECT_THAT(GetMatch(result_document_1, result_snippet_1, "body",
/*snippet_index=*/1),
Eq("Zürich"));
EXPECT_THAT(GetWindow(result_document_1, result_snippet_1, "body",
/*snippet_index=*/1),
Eq("mdi Zürich Team Meeting"));
const DocumentProto& result_document_2 = results.results(1).document();
const SnippetProto& result_snippet_2 = results.results(1).snippet();
EXPECT_THAT(result_document_2, EqualsProto(document_one));
EXPECT_THAT(GetMatch(result_document_2, result_snippet_2, "body",
/*snippet_index=*/0),
Eq("MDI"));
EXPECT_THAT(GetWindow(result_document_2, result_snippet_2, "body",
/*snippet_index=*/0),
Eq("MDI zurich Team Meeting"));
EXPECT_THAT(GetMatch(result_document_2, result_snippet_2, "body",
/*snippet_index=*/1),
Eq("zurich"));
EXPECT_THAT(GetWindow(result_document_2, result_snippet_2, "body",
/*snippet_index=*/1),
Eq("MDI zurich Team Meeting"));
}
TEST_F(IcingSearchEngineTest, SnippetSectionRestrict) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateEmailSchema()).status(), ProtoIsOk());
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Email")
.AddStringProperty("subject", "MDI zurich Team Meeting")
.AddStringProperty("body", "MDI zurich Team Meeting")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("body:Zür");
ResultSpecProto result_spec;
result_spec.mutable_snippet_spec()->set_max_window_bytes(64);
result_spec.mutable_snippet_spec()->set_num_matches_per_property(10);
result_spec.mutable_snippet_spec()->set_num_to_snippet(10);
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
ASSERT_THAT(results.results(), SizeIs(1));
const DocumentProto& result_document = results.results(0).document();
const SnippetProto& result_snippet = results.results(0).snippet();
EXPECT_THAT(result_document, EqualsProto(document_one));
EXPECT_THAT(
GetMatch(result_document, result_snippet, "body", /*snippet_index=*/0),
Eq("zurich"));
EXPECT_THAT(
GetWindow(result_document, result_snippet, "body", /*snippet_index=*/0),
Eq("MDI zurich Team Meeting"));
EXPECT_THAT(
GetMatch(result_document, result_snippet, "subject", /*snippet_index=*/0),
IsEmpty());
EXPECT_THAT(GetWindow(result_document, result_snippet, "subject",
/*snippet_index=*/0),
IsEmpty());
}
TEST_F(IcingSearchEngineTest, UninitializedInstanceFailsSafely) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
SchemaProto email_schema = CreateMessageSchema();
EXPECT_THAT(icing.SetSchema(email_schema).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.GetSchema().status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.GetSchemaType(email_schema.types(0).schema_type()).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
DocumentProto doc = CreateMessageDocument("namespace", "uri");
EXPECT_THAT(icing.Put(doc).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.Get(doc.namespace_(), doc.uri()).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.Delete(doc.namespace_(), doc.uri()).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.DeleteByNamespace(doc.namespace_()).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.DeleteBySchemaType(email_schema.types(0).schema_type())
.status()
.code(),
Eq(StatusProto::FAILED_PRECONDITION));
SearchSpecProto search_spec = SearchSpecProto::default_instance();
ScoringSpecProto scoring_spec = ScoringSpecProto::default_instance();
ResultSpecProto result_spec = ResultSpecProto::default_instance();
EXPECT_THAT(icing.Search(search_spec, scoring_spec, result_spec).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
constexpr int kSomePageToken = 12;
EXPECT_THAT(icing.GetNextPage(kSomePageToken).status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
icing.InvalidateNextPageToken(kSomePageToken); // Verify this doesn't crash.
EXPECT_THAT(icing.PersistToDisk().status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
EXPECT_THAT(icing.Optimize().status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
}
TEST_F(IcingSearchEngineTest, GetAllNamespaces) {
DocumentProto namespace1 = DocumentBuilder()
.SetKey("namespace1", "uri")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(100)
.SetTtlMs(1000)
.Build();
DocumentProto namespace2_uri1 = DocumentBuilder()
.SetKey("namespace2", "uri1")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(100)
.SetTtlMs(1000)
.Build();
DocumentProto namespace2_uri2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(100)
.SetTtlMs(1000)
.Build();
DocumentProto namespace3 = DocumentBuilder()
.SetKey("namespace3", "uri")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.SetCreationTimestampMs(100)
.SetTtlMs(500)
.Build();
{
// Some arbitrary time that's less than all the document's creation time +
// ttl
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(500);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// No namespaces exist yet
GetAllNamespacesResultProto result = icing.GetAllNamespaces();
EXPECT_THAT(result.status(), ProtoIsOk());
EXPECT_THAT(result.namespaces(), IsEmpty());
ASSERT_THAT(icing.Put(namespace1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(namespace2_uri1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(namespace2_uri2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(namespace3).status(), ProtoIsOk());
// All namespaces should exist now
result = icing.GetAllNamespaces();
EXPECT_THAT(result.status(), ProtoIsOk());
EXPECT_THAT(result.namespaces(),
UnorderedElementsAre("namespace1", "namespace2", "namespace3"));
// After deleting namespace2_uri1 document, we still have namespace2_uri2 in
// "namespace2" so it should still show up
ASSERT_THAT(icing.Delete("namespace2", "uri1").status(), ProtoIsOk());
result = icing.GetAllNamespaces();
EXPECT_THAT(result.status(), ProtoIsOk());
EXPECT_THAT(result.namespaces(),
UnorderedElementsAre("namespace1", "namespace2", "namespace3"));
// After deleting namespace2_uri2 document, we no longer have any documents
// in "namespace2"
ASSERT_THAT(icing.Delete("namespace2", "uri2").status(), ProtoIsOk());
result = icing.GetAllNamespaces();
EXPECT_THAT(result.status(), ProtoIsOk());
EXPECT_THAT(result.namespaces(),
UnorderedElementsAre("namespace1", "namespace3"));
}
// We reinitialize here so we can feed in a fake clock this time
{
// Time needs to be past namespace3's creation time (100) + ttl (500) for it
// to count as "expired"
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());
// Only valid document left is the one in "namespace1"
GetAllNamespacesResultProto result = icing.GetAllNamespaces();
EXPECT_THAT(result.status(), ProtoIsOk());
EXPECT_THAT(result.namespaces(), UnorderedElementsAre("namespace1"));
}
}
TEST_F(IcingSearchEngineTest, Hyphens) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema;
SchemaTypeConfigProto* type = schema.add_types();
type->set_schema_type("MyType");
PropertyConfigProto* prop = type->add_properties();
prop->set_property_name("foo");
prop->set_data_type(PropertyConfigProto::DataType::STRING);
prop->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
prop->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::EXACT_ONLY);
prop->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("MyType")
.AddStringProperty("foo", "foo bar-baz bat")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("MyType")
.AddStringProperty("foo", "bar for baz bat-man")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
search_spec.set_query("foo:bar-baz");
ResultSpecProto result_spec;
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
ASSERT_THAT(results.results(), SizeIs(2));
EXPECT_THAT(results.results(0).document(), EqualsProto(document_two));
EXPECT_THAT(results.results(1).document(), EqualsProto(document_one));
}
TEST_F(IcingSearchEngineTest, RestoreIndex) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", kIpsumText)
.Build();
// 1. Create an index with a LiteIndex that will only allow one document
// before needing a merge.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Add two documents. These should get merged into the main index.
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
document = DocumentBuilder(document).SetUri("fake_type/1").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// Add one document. This one should get remain in the lite index.
document = DocumentBuilder(document).SetUri("fake_type/2").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
}
// 2. Delete the index file to trigger RestoreIndexIfNeeded.
std::string idx_subdir = GetIndexDir() + "/idx";
filesystem()->DeleteDirectoryRecursively(idx_subdir.c_str());
// 3. Create the index again. This should trigger index restoration.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("consectetur");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto results =
icing.Search(search_spec, ScoringSpecProto::default_instance(),
ResultSpecProto::default_instance());
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.next_page_token(), Eq(0));
// All documents should be retrievable.
ASSERT_THAT(results.results(), SizeIs(3));
EXPECT_THAT(results.results(0).document().uri(), Eq("fake_type/2"));
EXPECT_THAT(results.results(1).document().uri(), Eq("fake_type/1"));
EXPECT_THAT(results.results(2).document().uri(), Eq("fake_type/0"));
}
}
TEST_F(IcingSearchEngineTest, RestoreIndexLoseLiteIndex) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", kIpsumText)
.Build();
// 1. Create an index with a LiteIndex that will only allow one document
// before needing a merge.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Add two documents. These should get merged into the main index.
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
document = DocumentBuilder(document).SetUri("fake_type/1").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// Add one document. This one should get remain in the lite index.
document = DocumentBuilder(document).SetUri("fake_type/2").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
}
// 2. Delete the last document from the document log
{
const std::string document_log_file =
absl_ports::StrCat(GetDocumentDir(), "/document_log");
filesystem()->DeleteFile(document_log_file.c_str());
ICING_ASSERT_OK_AND_ASSIGN(auto create_result,
FileBackedProtoLog<DocumentWrapper>::Create(
filesystem(), document_log_file.c_str(),
FileBackedProtoLog<DocumentWrapper>::Options(
/*compress_in=*/true)));
std::unique_ptr<FileBackedProtoLog<DocumentWrapper>> document_log =
std::move(create_result.proto_log);
document = DocumentBuilder(document).SetUri("fake_type/0").Build();
DocumentWrapper wrapper;
*wrapper.mutable_document() = document;
ASSERT_THAT(document_log->WriteProto(wrapper), IsOk());
document = DocumentBuilder(document).SetUri("fake_type/1").Build();
*wrapper.mutable_document() = document;
ASSERT_THAT(document_log->WriteProto(wrapper), IsOk());
}
// 3. Create the index again. This should throw out the lite index and trigger
// index restoration which will only restore the two documents in the main
// index.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("consectetur");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto results =
icing.Search(search_spec, ScoringSpecProto::default_instance(),
ResultSpecProto::default_instance());
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.next_page_token(), Eq(0));
// Only the documents that were in the main index should be retrievable.
ASSERT_THAT(results.results(), SizeIs(2));
EXPECT_THAT(results.results(0).document().uri(), Eq("fake_type/1"));
EXPECT_THAT(results.results(1).document().uri(), Eq("fake_type/0"));
}
}
TEST_F(IcingSearchEngineTest, RestoreIndexLoseIndex) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", kIpsumText)
.Build();
// 1. Create an index with a LiteIndex that will only allow one document
// before needing a merge.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Add two documents. These should get merged into the main index.
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
document = DocumentBuilder(document).SetUri("fake_type/1").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// Add one document. This one should get remain in the lite index.
document = DocumentBuilder(document).SetUri("fake_type/2").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
}
// 2. Delete the last two documents from the document log.
{
const std::string document_log_file =
absl_ports::StrCat(GetDocumentDir(), "/document_log");
filesystem()->DeleteFile(document_log_file.c_str());
ICING_ASSERT_OK_AND_ASSIGN(auto create_result,
FileBackedProtoLog<DocumentWrapper>::Create(
filesystem(), document_log_file.c_str(),
FileBackedProtoLog<DocumentWrapper>::Options(
/*compress_in=*/true)));
std::unique_ptr<FileBackedProtoLog<DocumentWrapper>> document_log =
std::move(create_result.proto_log);
document = DocumentBuilder(document).SetUri("fake_type/0").Build();
DocumentWrapper wrapper;
*wrapper.mutable_document() = document;
ASSERT_THAT(document_log->WriteProto(wrapper), IsOk());
}
// 3. Create the index again. This should throw out the lite and main index
// and trigger index restoration.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_query("consectetur");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto results =
icing.Search(search_spec, ScoringSpecProto::default_instance(),
ResultSpecProto::default_instance());
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.next_page_token(), Eq(0));
// Only the first document should be retrievable.
ASSERT_THAT(results.results(), SizeIs(1));
EXPECT_THAT(results.results(0).document().uri(), Eq("fake_type/0"));
}
}
TEST_F(IcingSearchEngineTest, IndexingDocMergeFailureResets) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", kIpsumText)
.Build();
// 1. Create an index with a LiteIndex that will only allow one document
// before needing a merge.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
IcingSearchEngine icing(options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Add two documents. These should get merged into the main index.
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
document = DocumentBuilder(document).SetUri("fake_type/1").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
// Add one document. This one should get remain in the lite index.
document = DocumentBuilder(document).SetUri("fake_type/2").Build();
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
}
// 2. Delete the index file to trigger RestoreIndexIfNeeded.
std::string idx_subdir = GetIndexDir() + "/idx";
filesystem()->DeleteDirectoryRecursively(idx_subdir.c_str());
// 3. Setup a mock filesystem to fail to grow the main index once.
bool has_failed_already = false;
auto open_write_lambda = [this, &has_failed_already](const char* filename) {
std::string main_lexicon_suffix = "/main-lexicon.prop.2";
std::string filename_string(filename);
if (!has_failed_already &&
filename_string.length() >= main_lexicon_suffix.length() &&
filename_string.substr(
filename_string.length() - main_lexicon_suffix.length(),
main_lexicon_suffix.length()) == main_lexicon_suffix) {
has_failed_already = true;
return -1;
}
return this->filesystem()->OpenForWrite(filename);
};
auto mock_icing_filesystem = std::make_unique<IcingMockFilesystem>();
ON_CALL(*mock_icing_filesystem, OpenForWrite)
.WillByDefault(open_write_lambda);
// 4. Create the index again. This should trigger index restoration.
{
IcingSearchEngineOptions options = GetDefaultIcingOptions();
options.set_index_merge_size(document.ByteSizeLong());
TestIcingSearchEngine icing(options, std::make_unique<Filesystem>(),
std::move(mock_icing_filesystem),
std::make_unique<FakeClock>(),
GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(),
ProtoStatusIs(StatusProto::WARNING_DATA_LOSS));
SearchSpecProto search_spec;
search_spec.set_query("consectetur");
search_spec.set_term_match_type(TermMatchType::EXACT_ONLY);
SearchResultProto results =
icing.Search(search_spec, ScoringSpecProto::default_instance(),
ResultSpecProto::default_instance());
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.next_page_token(), Eq(0));
// Only the last document that was added should still be retrievable.
ASSERT_THAT(results.results(), SizeIs(1));
EXPECT_THAT(results.results(0).document().uri(), Eq("fake_type/2"));
}
}
TEST_F(IcingSearchEngineTest, InitializeShouldLogFunctionLatency) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats().latency_ms(),
Eq(10));
}
TEST_F(IcingSearchEngineTest, InitializeShouldLogNumberOfDocuments) {
DocumentProto document1 = DocumentBuilder()
.SetKey("icing", "fake_type/1")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("icing", "fake_type/2")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put a document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(
initialize_result_proto.native_initialize_stats().num_documents(),
Eq(0));
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
}
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(
initialize_result_proto.native_initialize_stats().num_documents(),
Eq(1));
// Put another document.
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
}
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(
initialize_result_proto.native_initialize_stats().num_documents(),
Eq(2));
}
}
TEST_F(IcingSearchEngineTest,
InitializeShouldNotLogRecoveryCauseForFirstTimeInitialize) {
// Even though the fake timer will return 10, all the latency numbers related
// to recovery / restoration should be 0 during the first-time initialization.
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::NO_DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
TEST_F(IcingSearchEngineTest, InitializeShouldLogRecoveryCausePartialDataLoss) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put a document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
}
{
// Append a non-checksummed document. This will mess up the checksum of the
// proto log, forcing it to rewind and later return a DATA_LOSS error.
const std::string serialized_document = document.SerializeAsString();
const std::string document_log_file =
absl_ports::StrCat(GetDocumentDir(), "/document_log");
int64_t file_size = filesystem()->GetFileSize(document_log_file.c_str());
filesystem()->PWrite(document_log_file.c_str(), file_size,
serialized_document.data(),
serialized_document.size());
}
{
// Document store will rewind to previous checkpoint. The cause should be
// DATA_LOSS and the data status should be PARTIAL_LOSS.
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::PARTIAL_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
}
TEST_F(IcingSearchEngineTest,
InitializeShouldLogRecoveryCauseCompleteDataLoss) {
DocumentProto document1 = DocumentBuilder()
.SetKey("icing", "fake_type/1")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put a document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
}
{
// Modify the document log checksum to trigger a complete document log
// rewind.
const std::string document_log_file =
absl_ports::StrCat(GetDocumentDir(), "/document_log");
FileBackedProtoLog<DocumentWrapper>::Header document_log_header;
filesystem()->PRead(document_log_file.c_str(), &document_log_header,
sizeof(FileBackedProtoLog<DocumentWrapper>::Header),
/*offset=*/0);
// Set a garbage checksum.
document_log_header.log_checksum = 10;
document_log_header.header_checksum =
document_log_header.CalculateHeaderChecksum();
filesystem()->PWrite(document_log_file.c_str(), /*offset=*/0,
&document_log_header,
sizeof(FileBackedProtoLog<DocumentWrapper>::Header));
}
{
// Document store will completely rewind. The cause should be DATA_LOSS and
// the data status should be COMPLETE_LOSS.
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::COMPLETE_LOSS));
// The complete rewind of ground truth causes the mismatch of total
// checksum, so index should be restored.
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::TOTAL_CHECKSUM_MISMATCH));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
}
TEST_F(IcingSearchEngineTest,
InitializeShouldLogRecoveryCauseInconsistentWithGroundTruth) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put a document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document).status(), ProtoIsOk());
}
{
// Delete the index file to trigger RestoreIndexIfNeeded.
std::string idx_subdir = GetIndexDir() + "/idx";
filesystem()->DeleteDirectoryRecursively(idx_subdir.c_str());
}
{
// Index is empty but ground truth is not. Index should be restored due to
// the inconsistency.
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::INCONSISTENT_WITH_GROUND_TRUTH));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::NO_DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
}
TEST_F(IcingSearchEngineTest,
InitializeShouldLogRecoveryCauseTotalChecksumMismatch) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put one document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
}
{
// Change the header's checksum value to a random value.
uint32_t invalid_checksum = 1;
filesystem()->PWrite(GetHeaderFilename().c_str(),
offsetof(IcingSearchEngine::Header, checksum),
&invalid_checksum, sizeof(invalid_checksum));
}
{
// Both document store and index should be recovered from checksum mismatch.
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::TOTAL_CHECKSUM_MISMATCH));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::TOTAL_CHECKSUM_MISMATCH));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::NO_DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
}
TEST_F(IcingSearchEngineTest, InitializeShouldLogRecoveryCauseIndexIOError) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put one document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
}
// lambda to fail OpenForWrite on lite index hit buffer once.
bool has_failed_already = false;
auto open_write_lambda = [this, &has_failed_already](const char* filename) {
std::string lite_index_buffer_file_path =
absl_ports::StrCat(GetIndexDir(), "/idx/lite.hb");
std::string filename_string(filename);
if (!has_failed_already && filename_string == lite_index_buffer_file_path) {
has_failed_already = true;
return -1;
}
return this->filesystem()->OpenForWrite(filename);
};
auto mock_icing_filesystem = std::make_unique<IcingMockFilesystem>();
// This fails Index::Create() once.
ON_CALL(*mock_icing_filesystem, OpenForWrite)
.WillByDefault(open_write_lambda);
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::move(mock_icing_filesystem),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::IO_ERROR));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::NO_DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
TEST_F(IcingSearchEngineTest, InitializeShouldLogRecoveryCauseDocStoreIOError) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
{
// Initialize and put one document.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document).status(), ProtoIsOk());
}
// lambda to fail Read on document store header once.
bool has_failed_already = false;
auto read_lambda = [this, &has_failed_already](const char* filename,
void* buf, size_t buf_size) {
std::string document_store_header_file_path =
absl_ports::StrCat(GetDocumentDir(), "/document_store_header");
std::string filename_string(filename);
if (!has_failed_already &&
filename_string == document_store_header_file_path) {
has_failed_already = true;
return false;
}
return this->filesystem()->Read(filename, buf, buf_size);
};
auto mock_filesystem = std::make_unique<MockFilesystem>();
// This fails DocumentStore::InitializeDerivedFiles() once.
ON_CALL(*mock_filesystem, Read(A<const char*>(), _, _))
.WillByDefault(read_lambda);
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::move(mock_filesystem),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::IO_ERROR));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::NO_DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(0));
}
TEST_F(IcingSearchEngineTest,
InitializeShouldLogRecoveryCauseSchemaStoreIOError) {
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
}
{
// Delete the schema store header file to trigger an I/O error.
std::string schema_store_header_file_path =
GetSchemaDir() + "/schema_store_header";
filesystem()->DeleteFile(schema_store_header_file_path.c_str());
}
{
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_cause(),
Eq(NativeInitializeStats::IO_ERROR));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.schema_store_recovery_latency_ms(),
Eq(10));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_recovery_latency_ms(),
Eq(0));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.document_store_data_status(),
Eq(NativeInitializeStats::NO_DATA_LOSS));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_cause(),
Eq(NativeInitializeStats::NONE));
EXPECT_THAT(initialize_result_proto.native_initialize_stats()
.index_restoration_latency_ms(),
Eq(0));
}
}
TEST_F(IcingSearchEngineTest, InitializeShouldLogNumberOfSchemaTypes) {
{
// Initialize an empty storage.
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
// There should be 0 schema types.
EXPECT_THAT(
initialize_result_proto.native_initialize_stats().num_schema_types(),
Eq(0));
// Set a schema with one type config.
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
}
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
// There should be 1 schema type.
EXPECT_THAT(
initialize_result_proto.native_initialize_stats().num_schema_types(),
Eq(1));
// Create and set a schema with two type configs: Email and Message.
SchemaProto schema = CreateEmailSchema();
auto type = schema.add_types();
type->set_schema_type("Message");
auto body = type->add_properties();
body->set_property_name("body");
body->set_data_type(PropertyConfigProto::DataType::STRING);
body->set_cardinality(PropertyConfigProto::Cardinality::REQUIRED);
body->mutable_string_indexing_config()->set_term_match_type(
TermMatchType::PREFIX);
body->mutable_string_indexing_config()->set_tokenizer_type(
StringIndexingConfig::TokenizerType::PLAIN);
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
}
{
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
InitializeResultProto initialize_result_proto = icing.Initialize();
EXPECT_THAT(initialize_result_proto.status(), ProtoIsOk());
EXPECT_THAT(
initialize_result_proto.native_initialize_stats().num_schema_types(),
Eq(2));
}
}
TEST_F(IcingSearchEngineTest, PutDocumentShouldLogFunctionLatency) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
PutResultProto put_result_proto = icing.Put(document);
EXPECT_THAT(put_result_proto.status(), ProtoIsOk());
EXPECT_THAT(put_result_proto.native_put_document_stats().latency_ms(),
Eq(10));
}
TEST_F(IcingSearchEngineTest, PutDocumentShouldLogDocumentStoreStats) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
PutResultProto put_result_proto = icing.Put(document);
EXPECT_THAT(put_result_proto.status(), ProtoIsOk());
EXPECT_THAT(
put_result_proto.native_put_document_stats().document_store_latency_ms(),
Eq(10));
EXPECT_THAT(put_result_proto.native_put_document_stats().document_size(),
Eq(document.ByteSizeLong()));
}
TEST_F(IcingSearchEngineTest, PutDocumentShouldLogIndexingStats) {
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
PutResultProto put_result_proto = icing.Put(document);
EXPECT_THAT(put_result_proto.status(), ProtoIsOk());
EXPECT_THAT(put_result_proto.native_put_document_stats().index_latency_ms(),
Eq(10));
// No merge should happen.
EXPECT_THAT(
put_result_proto.native_put_document_stats().index_merge_latency_ms(),
Eq(0));
// Number of tokens should not exceed.
EXPECT_FALSE(put_result_proto.native_put_document_stats()
.tokenization_stats()
.exceeded_max_token_num());
// The input document has 2 tokens.
EXPECT_THAT(put_result_proto.native_put_document_stats()
.tokenization_stats()
.num_tokens_indexed(),
Eq(2));
}
TEST_F(IcingSearchEngineTest, PutDocumentShouldLogWhetherNumTokensExceeds) {
// Create a document with 2 tokens.
DocumentProto document = DocumentBuilder()
.SetKey("icing", "fake_type/0")
.SetSchema("Message")
.AddStringProperty("body", "message body")
.Build();
// Create an icing instance with max_tokens_per_doc = 1.
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
icing_options.set_max_tokens_per_doc(1);
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
PutResultProto put_result_proto = icing.Put(document);
EXPECT_THAT(put_result_proto.status(), ProtoIsOk());
// Number of tokens(2) exceeds the max allowed value(1).
EXPECT_TRUE(put_result_proto.native_put_document_stats()
.tokenization_stats()
.exceeded_max_token_num());
EXPECT_THAT(put_result_proto.native_put_document_stats()
.tokenization_stats()
.num_tokens_indexed(),
Eq(1));
}
TEST_F(IcingSearchEngineTest, PutDocumentShouldLogIndexMergeLatency) {
DocumentProto document1 = DocumentBuilder()
.SetKey("icing", "fake_type/1")
.SetSchema("Message")
.AddStringProperty("body", kIpsumText)
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("icing", "fake_type/2")
.SetSchema("Message")
.AddStringProperty("body", kIpsumText)
.Build();
// Create an icing instance with index_merge_size = document1's size.
IcingSearchEngineOptions icing_options = GetDefaultIcingOptions();
icing_options.set_index_merge_size(document1.ByteSizeLong());
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(10);
TestIcingSearchEngine icing(icing_options, std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
EXPECT_THAT(icing.Put(document1).status(), ProtoIsOk());
// Putting document2 should trigger an index merge.
PutResultProto put_result_proto = icing.Put(document2);
EXPECT_THAT(put_result_proto.status(), ProtoIsOk());
EXPECT_THAT(
put_result_proto.native_put_document_stats().index_merge_latency_ms(),
Eq(10));
}
TEST_F(IcingSearchEngineTest, SearchWithProjectionEmptyFieldPath) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
// 1. Add two email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.AddStringProperty("subject", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.AddStringProperty("subject", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk());
// 2. Issue a query that will match those documents and use an empty field
// mask to request NO properties.
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("hello");
ResultSpecProto result_spec;
// Retrieve only one result at a time to make sure that projection works when
// retrieving all pages.
result_spec.set_num_per_page(1);
ResultSpecProto::TypePropertyMask* email_field_mask =
result_spec.add_type_property_masks();
email_field_mask->set_schema_type("Email");
email_field_mask->add_paths("");
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.results(), SizeIs(1));
// 3. Verify that the returned results contain no properties.
DocumentProto projected_document_two = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.Build();
EXPECT_THAT(results.results(0).document(),
EqualsProto(projected_document_two));
results = icing.GetNextPage(results.next_page_token());
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.results(), SizeIs(1));
DocumentProto projected_document_one = DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.Build();
EXPECT_THAT(results.results(0).document(),
EqualsProto(projected_document_one));
}
TEST_F(IcingSearchEngineTest, SearchWithProjectionMultipleFieldPaths) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
// 1. Add two email documents
DocumentProto document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.AddStringProperty("emailAddress", "shopgirl@aol.com")
.Build())
.AddStringProperty("subject", "Hello World!")
.AddStringProperty(
"body", "Oh what a beautiful morning! Oh what a beautiful day!")
.Build();
ASSERT_THAT(icing.Put(document_one).status(), ProtoIsOk());
DocumentProto document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty(
"sender", DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.AddStringProperty("emailAddress", "ny152@aol.com")
.Build())
.AddStringProperty("subject", "Goodnight Moon!")
.AddStringProperty("body",
"Count all the sheep and tell them 'Hello'.")
.Build();
ASSERT_THAT(icing.Put(document_two).status(), ProtoIsOk());
// 2. Issue a query that will match those documents and request only
// 'sender.name' and 'subject' properties.
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.set_query("hello");
ResultSpecProto result_spec;
// Retrieve only one result at a time to make sure that projection works when
// retrieving all pages.
result_spec.set_num_per_page(1);
ResultSpecProto::TypePropertyMask* email_field_mask =
result_spec.add_type_property_masks();
email_field_mask->set_schema_type("Email");
email_field_mask->add_paths("sender.name");
email_field_mask->add_paths("subject");
SearchResultProto results =
icing.Search(search_spec, GetDefaultScoringSpec(), result_spec);
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.results(), SizeIs(1));
// 3. Verify that the returned results only contain the 'sender.name'
// property.
DocumentProto projected_document_two =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Person")
.AddStringProperty("name", "Tom Hanks")
.Build())
.AddStringProperty("subject", "Goodnight Moon!")
.Build();
EXPECT_THAT(results.results(0).document(),
EqualsProto(projected_document_two));
results = icing.GetNextPage(results.next_page_token());
EXPECT_THAT(results.status(), ProtoIsOk());
EXPECT_THAT(results.results(), SizeIs(1));
DocumentProto projected_document_one =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetCreationTimestampMs(1000)
.SetSchema("Email")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Person")
.AddStringProperty("name", "Meg Ryan")
.Build())
.AddStringProperty("subject", "Hello World!")
.Build();
EXPECT_THAT(results.results(0).document(),
EqualsProto(projected_document_one));
}
TEST_F(IcingSearchEngineTest, NativeQueryStatsTest) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetTimerElapsedMilliseconds(5);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreateMessageSchema()).status(), ProtoIsOk());
// Creates and inserts 5 documents
DocumentProto document1 = CreateMessageDocument("namespace", "uri1");
DocumentProto document2 = CreateMessageDocument("namespace", "uri2");
DocumentProto document3 = CreateMessageDocument("namespace", "uri3");
DocumentProto document4 = CreateMessageDocument("namespace", "uri4");
DocumentProto document5 = CreateMessageDocument("namespace", "uri5");
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document4).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document5).status(), ProtoIsOk());
SearchSpecProto search_spec;
search_spec.set_term_match_type(TermMatchType::PREFIX);
search_spec.add_namespace_filters("namespace");
search_spec.add_schema_type_filters(document1.schema());
search_spec.set_query("message");
ResultSpecProto result_spec;
result_spec.set_num_per_page(2);
result_spec.mutable_snippet_spec()->set_max_window_bytes(64);
result_spec.mutable_snippet_spec()->set_num_matches_per_property(1);
result_spec.mutable_snippet_spec()->set_num_to_snippet(3);
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::CREATION_TIMESTAMP);
// Searches and gets the first page, 2 results with 2 snippets
SearchResultProto search_result =
icing.Search(search_spec, scoring_spec, result_spec);
ASSERT_THAT(search_result.status(), ProtoIsOk());
ASSERT_THAT(search_result.results(), SizeIs(2));
ASSERT_THAT(search_result.next_page_token(), Ne(kInvalidNextPageToken));
// Check the stats
NativeQueryStats exp_stats;
exp_stats.set_num_terms(1);
exp_stats.set_num_namespaces_filtered(1);
exp_stats.set_num_schema_types_filtered(1);
exp_stats.set_ranking_strategy(
ScoringSpecProto::RankingStrategy::CREATION_TIMESTAMP);
exp_stats.set_is_first_page(true);
exp_stats.set_requested_page_size(2);
exp_stats.set_num_results_returned_current_page(2);
exp_stats.set_num_documents_scored(5);
exp_stats.set_num_results_snippeted(2);
exp_stats.set_latency_ms(5);
exp_stats.set_parse_query_latency_ms(5);
exp_stats.set_scoring_latency_ms(5);
exp_stats.set_ranking_latency_ms(5);
exp_stats.set_document_retrieval_latency_ms(5);
EXPECT_THAT(search_result.query_stats(), EqualsProto(exp_stats));
// Second page, 2 result with 1 snippet
search_result = icing.GetNextPage(search_result.next_page_token());
ASSERT_THAT(search_result.status(), ProtoIsOk());
ASSERT_THAT(search_result.results(), SizeIs(2));
ASSERT_THAT(search_result.next_page_token(), Gt(kInvalidNextPageToken));
exp_stats = NativeQueryStats();
exp_stats.set_is_first_page(false);
exp_stats.set_requested_page_size(2);
exp_stats.set_num_results_returned_current_page(2);
exp_stats.set_num_results_snippeted(1);
exp_stats.set_latency_ms(5);
exp_stats.set_document_retrieval_latency_ms(5);
EXPECT_THAT(search_result.query_stats(), EqualsProto(exp_stats));
// Third page, 1 result with 0 snippets
search_result = icing.GetNextPage(search_result.next_page_token());
ASSERT_THAT(search_result.status(), ProtoIsOk());
ASSERT_THAT(search_result.results(), SizeIs(1));
ASSERT_THAT(search_result.next_page_token(), Eq(kInvalidNextPageToken));
exp_stats = NativeQueryStats();
exp_stats.set_is_first_page(false);
exp_stats.set_requested_page_size(2);
exp_stats.set_num_results_returned_current_page(1);
exp_stats.set_num_results_snippeted(0);
exp_stats.set_latency_ms(5);
exp_stats.set_document_retrieval_latency_ms(5);
EXPECT_THAT(search_result.query_stats(), EqualsProto(exp_stats));
}
} // namespace
} // namespace lib
} // namespace icing