blob: b3aeafc997925999b74c9749d5a41f60ccb1ae55 [file]
// 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 <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include "icing/text_classifier/lib3/utils/base/status.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "icing/document-builder.h"
#include "icing/file/filesystem.h"
#include "icing/icing-search-engine.h"
#include "icing/jni/jni-cache.h"
#include "icing/portable/endian.h"
#include "icing/portable/equals-proto.h"
#include "icing/portable/platform.h"
#include "icing/proto/debug.pb.h"
#include "icing/proto/document.pb.h"
#include "icing/proto/document_wrapper.pb.h"
#include "icing/proto/initialize.pb.h"
#include "icing/proto/logging.pb.h"
#include "icing/proto/optimize.pb.h"
#include "icing/proto/persist.pb.h"
#include "icing/proto/reset.pb.h"
#include "icing/proto/schema.pb.h"
#include "icing/proto/scoring.pb.h"
#include "icing/proto/search.pb.h"
#include "icing/proto/status.pb.h"
#include "icing/proto/storage.pb.h"
#include "icing/proto/term.pb.h"
#include "icing/proto/usage.pb.h"
#include "icing/schema-builder.h"
#include "icing/testing/common-matchers.h"
#include "icing/testing/fake-clock.h"
#include "icing/testing/icu-data-file-helper.h"
#include "icing/testing/jni-test-helpers.h"
#include "icing/testing/test-data.h"
#include "icing/testing/tmp-directory.h"
namespace icing {
namespace lib {
namespace {
using ::icing::lib::portable_equals_proto::EqualsProto;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
// For mocking purpose, we allow tests to provide a custom Filesystem.
class TestIcingSearchEngine : public IcingSearchEngine {
public:
TestIcingSearchEngine(const IcingSearchEngineOptions& options,
std::unique_ptr<const Filesystem> filesystem,
std::unique_ptr<const IcingFilesystem> icing_filesystem,
std::unique_ptr<Clock> clock,
std::unique_ptr<JniCache> jni_cache)
: IcingSearchEngine(options, std::move(filesystem),
std::move(icing_filesystem), std::move(clock),
std::move(jni_cache)) {}
};
std::string GetTestBaseDir() { return GetTestTempDir() + "/icing"; }
// This test is meant to cover all tests relating to IcingSearchEngine::Search
// and IcingSearchEngine::SearchSuggestions.
class IcingSearchEngineSuggestTest : 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_;
};
IcingSearchEngineOptions GetDefaultIcingOptions() {
IcingSearchEngineOptions icing_options;
icing_options.set_base_dir(GetTestBaseDir());
return icing_options;
}
SchemaProto CreatePersonAndEmailSchema() {
return SchemaBuilder()
.AddType(SchemaTypeConfigBuilder()
.SetType("Person")
.AddProperty(PropertyConfigBuilder()
.SetName("name")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("emailAddress")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.AddType(
SchemaTypeConfigBuilder()
.SetType("Email")
.AddProperty(
PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(
PropertyConfigBuilder()
.SetName("subject")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("sender")
.SetDataTypeDocument(
"Person", /*index_nested_properties=*/true)
.SetCardinality(CARDINALITY_OPTIONAL)))
.Build();
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
// Creates and inserts 6 documents, and index 6 termSix, 5 termFive, 4
// termFour, 3 termThree, 2 termTwo and one termOne.
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty(
"subject", "termOne termTwo termThree termFour termFive termSix")
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject",
"termTwo termThree termFour termFive termSix")
.Build();
DocumentProto document3 =
DocumentBuilder()
.SetKey("namespace", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termThree termFour termFive termSix")
.Build();
DocumentProto document4 =
DocumentBuilder()
.SetKey("namespace", "uri4")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termFour termFive termSix")
.Build();
DocumentProto document5 =
DocumentBuilder()
.SetKey("namespace", "uri5")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termFive termSix")
.Build();
DocumentProto document6 = DocumentBuilder()
.SetKey("namespace", "uri6")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termSix")
.Build();
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());
ASSERT_THAT(icing.Put(document6).status(), ProtoIsOk());
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("t");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
// Query all suggestions, and they will be ranked.
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions().at(0).query(), "termsix");
ASSERT_THAT(response.suggestions().at(1).query(), "termfive");
ASSERT_THAT(response.suggestions().at(2).query(), "termfour");
ASSERT_THAT(response.suggestions().at(3).query(), "termthree");
ASSERT_THAT(response.suggestions().at(4).query(), "termtwo");
ASSERT_THAT(response.suggestions().at(5).query(), "termone");
// Query first three suggestions, and they will be ranked.
suggestion_spec.set_num_to_return(3);
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions().at(0).query(), "termsix");
ASSERT_THAT(response.suggestions().at(1).query(), "termfive");
ASSERT_THAT(response.suggestions().at(2).query(), "termfour");
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInOneNamespace) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
// namespace1 has 2 results.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.add_namespace_filters("namespace1");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFoo),
EqualsProto(suggestionFool)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInMultipleNamespace) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fo")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace3", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
// namespace2 and namespace3 has 2 results.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.add_namespace_filters("namespace2");
suggestion_spec.add_namespace_filters("namespace3");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFoo),
EqualsProto(suggestionFool)));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_NamespaceNotFound) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fo")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// Search for non-exist namespace3
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.add_namespace_filters("namespace3");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
EXPECT_THAT(response.status().code(), Eq(StatusProto::OK));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_OtherNamespaceDontContributeToHitCount) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
// Index 4 documents,
// namespace1 has 2 hit2 for term one
// namespace2 has 2 hit2 for term two and 1 hit for term one.
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termone")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termone")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termone termtwo")
.Build();
DocumentProto document4 = DocumentBuilder()
.SetKey("namespace2", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "termtwo")
.Build();
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());
SuggestionResponse::Suggestion suggestionTermOne;
suggestionTermOne.set_query("termone");
SuggestionResponse::Suggestion suggestionTermTwo;
suggestionTermTwo.set_query("termtwo");
// only search suggestion for namespace2. The correctly order should be
// {"termtwo", "termone"}. If we're not filtering out namespace1 when
// calculating our score, then it will be {"termone", "termtwo"}.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("t");
suggestion_spec.add_namespace_filters("namespace2");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
ElementsAre(EqualsProto(suggestionTermTwo),
EqualsProto(suggestionTermOne)));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_DeletionTest) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
// namespace1 has this suggestion
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.add_namespace_filters("namespace1");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
// namespace2 has this suggestion
suggestion_spec.clear_namespace_filters();
suggestion_spec.add_namespace_filters("namespace2");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
// delete document from namespace 1
EXPECT_THAT(icing.Delete("namespace1", "uri1").status(), ProtoIsOk());
// Now namespace1 will return empty
suggestion_spec.clear_namespace_filters();
suggestion_spec.add_namespace_filters("namespace1");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(), IsEmpty());
// namespace2 still has this suggestion, so we can prove the reason of
// namespace 1 cannot find it is we filter it out, not it doesn't exist.
suggestion_spec.add_namespace_filters("namespace2");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInOneDocument) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
// Only search in namespace1,uri1
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
NamespaceDocumentUriGroup* namespace1_uri1 =
suggestion_spec.add_document_uri_filters();
namespace1_uri1->set_namespace_("namespace1");
namespace1_uri1->add_document_uris("uri1");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
// Only search in namespace1,uri2
suggestion_spec.clear_document_uri_filters();
NamespaceDocumentUriGroup* namespace1_uri2 =
suggestion_spec.add_document_uri_filters();
namespace1_uri2->set_namespace_("namespace1");
namespace1_uri2->add_document_uris("uri2");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFoo)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInMultipleDocument) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace1", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
// Only search document in namespace1,uri1 and namespace2,uri2
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
NamespaceDocumentUriGroup* namespace1_uri1_uri2 =
suggestion_spec.add_document_uri_filters();
namespace1_uri1_uri2->set_namespace_("namespace1");
namespace1_uri1_uri2->add_document_uris("uri1");
namespace1_uri1_uri2->add_document_uris("uri2");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool),
EqualsProto(suggestionFoo)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInDesiredDocumentAndNamespace) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace3", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
// Only search document in namespace1,uri1 and all documents under namespace2
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
suggestion_spec.add_namespace_filters("namespace1");
suggestion_spec.add_namespace_filters("namespace2");
NamespaceDocumentUriGroup* namespace1_uri1 =
suggestion_spec.add_document_uri_filters();
namespace1_uri1->set_namespace_("namespace1");
namespace1_uri1->add_document_uris("uri1");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool),
EqualsProto(suggestionFoo)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_DocumentIdDoesntExist) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// Search for a non-exist document id : namespace3,uri3
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
suggestion_spec.add_namespace_filters("namespace3");
NamespaceDocumentUriGroup* namespace3_uri3 =
suggestion_spec.add_document_uri_filters();
namespace3_uri3->set_namespace_("namespace3");
namespace3_uri3->add_document_uris("uri3");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(), IsEmpty());
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_DocumentIdFilterDoesntMatchNamespaceFilter) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
// Search for the document namespace1,uri1 with namespace filter in
// namespace2.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
NamespaceDocumentUriGroup* namespace1_uri1 =
suggestion_spec.add_document_uri_filters();
namespace1_uri1->set_namespace_("namespace1");
namespace1_uri1->add_document_uris("uri1");
suggestion_spec.add_namespace_filters("namespace2");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
EXPECT_THAT(response.status().code(), Eq(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_EmptyDocumentIdInNamespace) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
// Give empty document uris in namespace 1
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
NamespaceDocumentUriGroup* namespace1_uri1 =
suggestion_spec.add_document_uri_filters();
namespace1_uri1->set_namespace_("namespace1");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
EXPECT_THAT(response.status().code(), Eq(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInDesiredSchemaType) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty(
PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REQUIRED)))
.AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty(
PropertyConfigBuilder()
.SetName("name")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.AddType(SchemaTypeConfigBuilder()
.SetType("Email")
.AddProperty(PropertyConfigBuilder()
.SetName("sender")
.SetDataTypeDocument(
"Person",
/*index_nested_properties=*/true)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("subject")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.Build();
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.AddDocumentProperty("sender", DocumentBuilder()
.SetKey("namespace", "uri1-sender")
.SetSchema("Person")
.AddStringProperty("name", "foo")
.Build())
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Message")
.SetCreationTimestampMs(10)
.AddStringProperty("body", "fo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
suggestion_spec.add_schema_type_filters("Email");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFoo),
EqualsProto(suggestionFool)));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_SchemaTypeNotFound) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty(
PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REQUIRED)))
.Build();
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.SetCreationTimestampMs(10)
.AddStringProperty("body", "fo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
suggestion_spec.add_schema_type_filters("Email");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(), IsEmpty());
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_ShouldReturnInDesiredProperty) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri1-sender")
.SetSchema("Person")
.AddStringProperty("name", "foo")
.AddStringProperty("emailAddress", "fo")
.Build())
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
// Only search in subject.
TypePropertyMask* mask = suggestion_spec.add_type_property_filters();
mask->set_schema_type("Email");
mask->add_paths("subject");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
// Search in subject and sender.name
suggestion_spec.clear_type_property_filters();
mask = suggestion_spec.add_type_property_filters();
mask->set_schema_type("Email");
mask->add_paths("subject");
mask->add_paths("sender.name");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFoo),
EqualsProto(suggestionFool)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_NestedPropertyReturnNothing) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.AddDocumentProperty("sender", DocumentBuilder()
.SetKey("namespace", "uri1-sender")
.SetSchema("Person")
.AddStringProperty("name", "foo")
.Build())
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
// Only search in Person.name.
suggestion_spec.add_schema_type_filters("Person");
TypePropertyMask* mask = suggestion_spec.add_type_property_filters();
mask->set_schema_type("Person");
mask->add_paths("name");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(), IsEmpty());
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_PropertyFilterAndSchemaFilter) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty(
PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REQUIRED)))
.AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty(
PropertyConfigBuilder()
.SetName("name")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.AddType(SchemaTypeConfigBuilder()
.SetType("Email")
.AddProperty(PropertyConfigBuilder()
.SetName("sender")
.SetDataTypeDocument(
"Person",
/*index_nested_properties=*/true)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("subject")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.Build();
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.AddDocumentProperty("sender", DocumentBuilder()
.SetKey("namespace", "uri1-sender")
.SetSchema("Person")
.AddStringProperty("name", "foo")
.Build())
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Message")
.SetCreationTimestampMs(10)
.AddStringProperty("body", "fo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFoo;
suggestionFoo.set_query("foo");
SuggestionResponse::Suggestion suggestionFo;
suggestionFo.set_query("fo");
// Search in sender.name of Email and everything in Message.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
suggestion_spec.add_schema_type_filters("Email");
suggestion_spec.add_schema_type_filters("Message");
TypePropertyMask* mask1 = suggestion_spec.add_type_property_filters();
mask1->set_schema_type("Email");
mask1->add_paths("sender.name");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFoo),
EqualsProto(suggestionFo)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_PropertyFilterNotMatchSchemaFilter) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty(
PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REQUIRED)))
.AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty(
PropertyConfigBuilder()
.SetName("name")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.AddType(SchemaTypeConfigBuilder()
.SetType("Email")
.AddProperty(PropertyConfigBuilder()
.SetName("sender")
.SetDataTypeDocument(
"Person",
/*index_nested_properties=*/true)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName("subject")
.SetDataTypeString(TERM_MATCH_PREFIX,
TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)))
.Build();
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.SetCreationTimestampMs(10)
.AddStringProperty("body", "fo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
// Search in sender.name of Email but schema type is Message.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
suggestion_spec.add_schema_type_filters("Message");
TypePropertyMask* mask1 = suggestion_spec.add_type_property_filters();
mask1->set_schema_type("Email");
mask1->add_paths("sender.name");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
EXPECT_THAT(response.status().code(), Eq(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_OrderByTermFrequency) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SchemaProto schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty(
PropertyConfigBuilder()
.SetName("body")
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REQUIRED)))
.Build();
ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Message")
.SetCreationTimestampMs(10)
.AddStringProperty(
"body", "termthree termthree termthree termtwo termtwo termone")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
// Search in sender.name of Email but schema type is Message.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("t");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::EXACT_ONLY);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::TERM_FREQUENCY);
SuggestionResponse::Suggestion suggestionTermOne;
suggestionTermOne.set_query("termone");
SuggestionResponse::Suggestion suggestionTermTwo;
suggestionTermTwo.set_query("termtwo");
SuggestionResponse::Suggestion suggestionTermThree;
suggestionTermThree.set_query("termthree");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
ElementsAre(EqualsProto(suggestionTermThree),
EqualsProto(suggestionTermTwo),
EqualsProto(suggestionTermOne)));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_ExpiredTest) {
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(100)
.SetTtlMs(500)
.AddStringProperty("subject", "fool")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace2", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(100)
.SetTtlMs(1000)
.AddStringProperty("subject", "fool")
.Build();
{
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());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
// namespace1 has this suggestion
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.add_namespace_filters("namespace1");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
// namespace2 has this suggestion
suggestion_spec.clear_namespace_filters();
suggestion_spec.add_namespace_filters("namespace2");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
}
// We reinitialize here so we can feed in a fake clock this time
{
// Time needs to be past document1 creation time (100) + ttl (500) for it
// to count as "expired". document2 is not expired since its ttl is 1000.
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(800);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f");
suggestion_spec.add_namespace_filters("namespace1");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
// Now namespace1 will return empty
suggestion_spec.clear_namespace_filters();
suggestion_spec.add_namespace_filters("namespace1");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(), IsEmpty());
// namespace2 still has this suggestion
SuggestionResponse::Suggestion suggestionFool;
suggestionFool.set_query("fool");
suggestion_spec.add_namespace_filters("namespace2");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionFool)));
}
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_emptyPrefix) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
ASSERT_THAT(icing.SearchSuggestions(suggestion_spec).status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_NonPositiveNumToReturn) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("prefix");
suggestion_spec.set_num_to_return(0);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
ASSERT_THAT(icing.SearchSuggestions(suggestion_spec).status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_MultipleTerms_And) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "bar fo")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "foo")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionBarFo;
suggestionBarFo.set_query("bar fo");
// Search "bar AND f" only document 1 should match the search.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("bar f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionBarFo)));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_MultipleTerms_Or) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 = DocumentBuilder()
.SetKey("namespace", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "bar fo")
.Build();
DocumentProto document2 = DocumentBuilder()
.SetKey("namespace", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "cat foo")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
SuggestionResponse::Suggestion suggestionBarCatFo;
suggestionBarCatFo.set_query("bar OR cat fo");
SuggestionResponse::Suggestion suggestionBarCatFoo;
suggestionBarCatFoo.set_query("bar OR cat foo");
// Search for "(bar OR cat) AND f" both document1 "bar fo" and document2 "cat
// foo" could match.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("bar OR cat f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionBarCatFo),
EqualsProto(suggestionBarCatFoo)));
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_PropertyRestriction) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool")
.AddDocumentProperty("sender",
DocumentBuilder()
.SetKey("namespace", "uri1-sender")
.SetSchema("Person")
.AddStringProperty("name", "foo")
.AddStringProperty("emailAddress", "fo")
.Build())
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
// Add property restriction, only search for subject.
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("subject:f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse::Suggestion suggestionSubjectFool;
suggestionSubjectFool.set_query("subject:fool");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionSubjectFool)));
// Add property restriction, only search for nested sender.name
suggestion_spec.set_prefix("sender.name:f");
SuggestionResponse::Suggestion suggestionSenderNameFoo;
suggestionSenderNameFoo.set_query("sender.name:foo");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionSenderNameFoo)));
// Add property restriction, only search for nonExist section
suggestion_spec.set_prefix("none:f");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(), IsEmpty());
}
TEST_F(IcingSearchEngineSuggestTest,
SearchSuggestionsTest_AndOperatorPlusPropertyRestriction) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "bar fo") // "bar fo"
.AddStringProperty("body", "fool")
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "bar cat foo") // "bar cat fool"
.AddStringProperty("body", "fool")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace1", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool") // "fool"
.AddStringProperty("body", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
// Search for "bar AND subject:f"
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("bar subject:f");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse::Suggestion suggestionBarSubjectFo;
suggestionBarSubjectFo.set_query("bar subject:fo");
SuggestionResponse::Suggestion suggestionBarSubjectFoo;
suggestionBarSubjectFoo.set_query("bar subject:foo");
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionBarSubjectFo),
EqualsProto(suggestionBarSubjectFoo)));
// Search for "bar AND cat AND subject:f"
suggestion_spec.set_prefix("bar cat subject:f");
SuggestionResponse::Suggestion suggestionBarCatSubjectFoo;
suggestionBarCatSubjectFoo.set_query("bar cat subject:foo");
response = icing.SearchSuggestions(suggestion_spec);
ASSERT_THAT(response.status(), ProtoIsOk());
ASSERT_THAT(response.suggestions(),
UnorderedElementsAre(EqualsProto(suggestionBarCatSubjectFoo)));
}
TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_InvalidPrefixTest) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(),
ProtoIsOk());
DocumentProto document1 =
DocumentBuilder()
.SetKey("namespace1", "uri1")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "bar fo") // "bar fo"
.AddStringProperty("body", "fool")
.Build();
DocumentProto document2 =
DocumentBuilder()
.SetKey("namespace1", "uri2")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "bar cat foo") // "bar cat fool"
.AddStringProperty("body", "fool")
.Build();
DocumentProto document3 = DocumentBuilder()
.SetKey("namespace1", "uri3")
.SetSchema("Email")
.SetCreationTimestampMs(10)
.AddStringProperty("subject", "fool") // "fool"
.AddStringProperty("body", "fool")
.Build();
ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk());
ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk());
// Search for "f OR"
SuggestionSpecProto suggestion_spec;
suggestion_spec.set_prefix("f OR");
suggestion_spec.set_num_to_return(10);
suggestion_spec.mutable_scoring_spec()->set_scoring_match_type(
TermMatchType::PREFIX);
suggestion_spec.mutable_scoring_spec()->set_rank_by(
SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT);
SuggestionResponse response = icing.SearchSuggestions(suggestion_spec);
if (SearchSpecProto::default_instance().search_type() ==
SearchSpecProto::SearchType::ICING_RAW_QUERY) {
EXPECT_THAT(response.status(), ProtoIsOk());
EXPECT_THAT(response.suggestions(), IsEmpty());
} else {
EXPECT_THAT(response.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
EXPECT_THAT(response.suggestions(), IsEmpty());
}
// TODO(b/208654892): Update handling for hyphens to only consider it a hyphen
// within a TEXT token (rather than a MINUS token) when surrounded on both
// sides by TEXT rather than just preceded by TEXT.
// Search for "f-"
suggestion_spec.set_prefix("f-");
response = icing.SearchSuggestions(suggestion_spec);
EXPECT_THAT(response.status(), ProtoIsOk());
EXPECT_THAT(response.suggestions(), IsEmpty());
// Search for "f:"
suggestion_spec.set_prefix("f:");
response = icing.SearchSuggestions(suggestion_spec);
if (SearchSpecProto::default_instance().search_type() ==
SearchSpecProto::SearchType::ICING_RAW_QUERY) {
EXPECT_THAT(response.status(), ProtoIsOk());
EXPECT_THAT(response.suggestions(), IsEmpty());
} else {
EXPECT_THAT(response.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
EXPECT_THAT(response.suggestions(), IsEmpty());
}
// Search for "OR OR - :"
suggestion_spec.set_prefix("OR OR - :");
response = icing.SearchSuggestions(suggestion_spec);
if (SearchSpecProto::default_instance().search_type() ==
SearchSpecProto::SearchType::ICING_RAW_QUERY) {
EXPECT_THAT(response.status(), ProtoIsOk());
EXPECT_THAT(response.suggestions(), IsEmpty());
} else {
EXPECT_THAT(response.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
EXPECT_THAT(response.suggestions(), IsEmpty());
}
}
} // namespace
} // namespace lib
} // namespace icing