blob: eafc6127a53899e853909b9d5156906b9a962c00 [file] [edit]
// Copyright (C) 2023 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/schema/schema-type-manager.h"
#include <memory>
#include <string>
#include <string_view>
#include "icing/text_classifier/lib3/utils/base/status.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "icing/file/filesystem.h"
#include "icing/proto/schema.pb.h"
#include "icing/schema-builder.h"
#include "icing/schema/schema-util.h"
#include "icing/schema/section.h"
#include "icing/store/dynamic-trie-key-mapper.h"
#include "icing/store/key-mapper.h"
#include "icing/testing/common-matchers.h"
#include "icing/testing/tmp-directory.h"
namespace icing {
namespace lib {
namespace {
using ::testing::ElementsAre;
using ::testing::Pointee;
// type and property names of EmailMessage
static constexpr char kTypeEmail[] = "EmailMessage";
static constexpr SchemaTypeId kTypeEmailSchemaId = 0;
// indexable (in lexicographical order)
static constexpr char kPropertyRecipientIds[] = "recipientIds";
static constexpr char kPropertyRecipients[] = "recipients";
static constexpr char kPropertySenderQualifiedId[] =
"senderQualifiedId"; // QUALIFIED_ID joinable
static constexpr char kPropertySubject[] = "subject";
static constexpr char kPropertyTimestamp[] = "timestamp";
// non-indexable
static constexpr char kPropertyAttachment[] = "attachment";
static constexpr char kPropertyNonIndexableInteger[] = "nonIndexableInteger";
static constexpr char kPropertyTagQualifiedId[] =
"tagQualifiedId"; // QUALIFIED_ID joinable
static constexpr char kPropertyText[] = "text";
// type and property names of Conversation
static constexpr char kTypeConversation[] = "Conversation";
static constexpr SchemaTypeId kTypeConversationSchemaId = 1;
// indexable (in lexicographical order)
static constexpr char kPropertyEmails[] = "emails";
static constexpr char kPropertyGroupQualifiedId[] =
"groupQualifiedId"; // QUALIFIED_ID joinable
static constexpr char kPropertyName[] = "name";
// non-indexable
static constexpr char kPropertyNestedNonIndexable[] = "nestedNonIndexable";
static constexpr char kPropertySuperTagQualifiedId[] =
"superTagQualifiedId"; // QUALIFIED_ID joinable
PropertyConfigProto CreateReceipientIdsPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertyRecipientIds)
.SetDataTypeInt64(NUMERIC_MATCH_RANGE)
.SetCardinality(CARDINALITY_REPEATED)
.Build();
}
PropertyConfigProto CreateRecipientsPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertyRecipients)
.SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REPEATED)
.Build();
}
PropertyConfigProto CreateSenderQualifiedIdPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertySenderQualifiedId)
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID, /*propagate_delete=*/true)
.SetCardinality(CARDINALITY_REQUIRED)
.Build();
}
PropertyConfigProto CreateSubjectPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertySubject)
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_REQUIRED)
.Build();
}
PropertyConfigProto CreateTimestampPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertyTimestamp)
.SetDataTypeInt64(NUMERIC_MATCH_RANGE)
.SetCardinality(CARDINALITY_REQUIRED)
.Build();
}
PropertyConfigProto CreateTagQualifiedIdPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertyTagQualifiedId)
.SetDataType(TYPE_STRING)
.SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID, /*propagate_delete=*/true)
.SetCardinality(CARDINALITY_REQUIRED)
.Build();
}
PropertyConfigProto CreateGroupQualifiedIdPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertyGroupQualifiedId)
.SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN)
.SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID, /*propagate_delete=*/true)
.SetCardinality(CARDINALITY_REQUIRED)
.Build();
}
PropertyConfigProto CreateSuperTagQualifiedIdPropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertySuperTagQualifiedId)
.SetDataType(TYPE_STRING)
.SetJoinable(JOINABLE_VALUE_TYPE_QUALIFIED_ID, /*propagate_delete=*/true)
.SetCardinality(CARDINALITY_REQUIRED)
.Build();
}
PropertyConfigProto CreateNamePropertyConfig() {
return PropertyConfigBuilder()
.SetName(kPropertyName)
.SetDataTypeString(TERM_MATCH_EXACT, TOKENIZER_PLAIN)
.SetCardinality(CARDINALITY_OPTIONAL)
.Build();
}
SchemaTypeConfigProto CreateEmailTypeConfig() {
return SchemaTypeConfigBuilder()
.SetType(kTypeEmail)
.AddProperty(CreateTagQualifiedIdPropertyConfig())
.AddProperty(CreateSubjectPropertyConfig())
.AddProperty(PropertyConfigBuilder()
.SetName(kPropertyText)
.SetDataTypeString(TERM_MATCH_UNKNOWN, TOKENIZER_NONE)
.SetCardinality(CARDINALITY_OPTIONAL))
.AddProperty(PropertyConfigBuilder()
.SetName(kPropertyAttachment)
.SetDataType(TYPE_BYTES)
.SetCardinality(CARDINALITY_REQUIRED))
.AddProperty(CreateSenderQualifiedIdPropertyConfig())
.AddProperty(CreateRecipientsPropertyConfig())
.AddProperty(CreateReceipientIdsPropertyConfig())
.AddProperty(CreateTimestampPropertyConfig())
.AddProperty(PropertyConfigBuilder()
.SetName(kPropertyNonIndexableInteger)
.SetDataType(TYPE_INT64)
.SetCardinality(CARDINALITY_REQUIRED))
.Build();
}
SchemaTypeConfigProto CreateConversationTypeConfig() {
return SchemaTypeConfigBuilder()
.SetType(kTypeConversation)
.AddProperty(CreateSuperTagQualifiedIdPropertyConfig())
.AddProperty(CreateNamePropertyConfig())
.AddProperty(CreateGroupQualifiedIdPropertyConfig())
.AddProperty(
PropertyConfigBuilder()
.SetName(kPropertyEmails)
.SetDataTypeDocument(kTypeEmail, /*index_nested_properties=*/true)
.SetCardinality(CARDINALITY_REPEATED))
.AddProperty(PropertyConfigBuilder()
.SetName(kPropertyNestedNonIndexable)
.SetDataTypeDocument(kTypeEmail,
/*index_nested_properties=*/false)
.SetCardinality(CARDINALITY_REPEATED))
.Build();
}
class SchemaTypeManagerTest : public ::testing::Test {
protected:
void SetUp() override { test_dir_ = GetTestTempDir() + "/icing"; }
void TearDown() override {
filesystem_.DeleteDirectoryRecursively(test_dir_.c_str());
}
Filesystem filesystem_;
std::string test_dir_;
};
TEST_F(SchemaTypeManagerTest, Create) {
SchemaUtil::TypeConfigMap type_config_map;
type_config_map.emplace(kTypeEmail, CreateEmailTypeConfig());
type_config_map.emplace(kTypeConversation, CreateConversationTypeConfig());
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<KeyMapper<SchemaTypeId>> schema_type_mapper,
DynamicTrieKeyMapper<SchemaTypeId>::Create(
filesystem_, test_dir_ + "/schema_type_mapper",
/*maximum_size_bytes=*/3 * 128 * 1024));
ICING_ASSERT_OK(schema_type_mapper->Put(kTypeEmail, kTypeEmailSchemaId));
ICING_ASSERT_OK(
schema_type_mapper->Put(kTypeConversation, kTypeConversationSchemaId));
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<SchemaTypeManager> schema_type_manager,
SchemaTypeManager::Create(type_config_map, schema_type_mapper.get()));
// Check SectionManager
// In the Email type, "recipientIds", "recipients", "senderQualifiedId",
// "subject" and "timestamp" are indexable properties. "attachment",
// "nonIndexableInteger", "tagQualifiedId" and "text" are non-indexable.
EXPECT_THAT(
schema_type_manager->section_manager().GetMetadataList(kTypeEmail),
IsOkAndHolds(Pointee(ElementsAre(
EqualsSectionMetadata(/*expected_id=*/0,
/*expected_property_path=*/"recipientIds",
CreateReceipientIdsPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/1,
/*expected_property_path=*/"recipients",
CreateRecipientsPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/2,
/*expected_property_path=*/"senderQualifiedId",
CreateSenderQualifiedIdPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/3,
/*expected_property_path=*/"subject",
CreateSubjectPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/4,
/*expected_property_path=*/"timestamp",
CreateTimestampPropertyConfig())))));
// In the Conversation type, "groupQualifiedId" and "name" are indexable
// properties as are the indexable properties of the email in the "emails"
// property. All properties of the email in the "nestedNonIndexable" property
// are not indexable.
EXPECT_THAT(
schema_type_manager->section_manager().GetMetadataList(kTypeConversation),
IsOkAndHolds(Pointee(ElementsAre(
EqualsSectionMetadata(
/*expected_id=*/0,
/*expected_property_path=*/"emails.recipientIds",
CreateReceipientIdsPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/1,
/*expected_property_path=*/"emails.recipients",
CreateRecipientsPropertyConfig()),
EqualsSectionMetadata(
/*expected_id=*/2,
/*expected_property_path=*/"emails.senderQualifiedId",
CreateSenderQualifiedIdPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/3,
/*expected_property_path=*/"emails.subject",
CreateSubjectPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/4,
/*expected_property_path=*/"emails.timestamp",
CreateTimestampPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/5,
/*expected_property_path=*/"groupQualifiedId",
CreateGroupQualifiedIdPropertyConfig()),
EqualsSectionMetadata(/*expected_id=*/6,
/*expected_property_path=*/"name",
CreateNamePropertyConfig())))));
// Check JoinablePropertyManager
// In the Email type, "senderQualifiedId" and "tagQualifiedId" are joinable
// properties.
EXPECT_THAT(
schema_type_manager->joinable_property_manager().GetMetadataList(
kTypeEmail),
IsOkAndHolds(Pointee(ElementsAre(
EqualsJoinablePropertyMetadata(
/*expected_id=*/0, /*expected_property_path=*/"senderQualifiedId",
CreateSenderQualifiedIdPropertyConfig()),
EqualsJoinablePropertyMetadata(
/*expected_id=*/1, /*expected_property_path=*/"tagQualifiedId",
CreateTagQualifiedIdPropertyConfig())))));
// In the Conversation type, "groupQualifiedId" and "superTagQualifiedId" are
// joinable properties as are the joinable properties of the email in the
// "emails" and "nestedNonIndexable" property.
EXPECT_THAT(
schema_type_manager->joinable_property_manager().GetMetadataList(
kTypeConversation),
IsOkAndHolds(Pointee(ElementsAre(
EqualsJoinablePropertyMetadata(
/*expected_id=*/0,
/*expected_property_path=*/"emails.senderQualifiedId",
CreateSenderQualifiedIdPropertyConfig()),
EqualsJoinablePropertyMetadata(
/*expected_id=*/1,
/*expected_property_path=*/"emails.tagQualifiedId",
CreateTagQualifiedIdPropertyConfig()),
EqualsJoinablePropertyMetadata(
/*expected_id=*/2, /*expected_property_path=*/"groupQualifiedId",
CreateGroupQualifiedIdPropertyConfig()),
EqualsJoinablePropertyMetadata(
/*expected_id=*/3,
/*expected_property_path=*/"nestedNonIndexable.senderQualifiedId",
CreateSenderQualifiedIdPropertyConfig()),
EqualsJoinablePropertyMetadata(
/*expected_id=*/4,
/*expected_property_path=*/"nestedNonIndexable.tagQualifiedId",
CreateTagQualifiedIdPropertyConfig()),
EqualsJoinablePropertyMetadata(
/*expected_id=*/5,
/*expected_property_path=*/"superTagQualifiedId",
CreateSuperTagQualifiedIdPropertyConfig())))));
}
TEST_F(SchemaTypeManagerTest, CreateWithNullPointerShouldFail) {
SchemaUtil::TypeConfigMap type_config_map;
EXPECT_THAT(SchemaTypeManager::Create(type_config_map,
/*schema_type_mapper=*/nullptr),
StatusIs(libtextclassifier3::StatusCode::FAILED_PRECONDITION));
}
TEST_F(SchemaTypeManagerTest, CreateWithSchemaNotInSchemaTypeMapperShouldFail) {
SchemaTypeConfigProto type_config;
type_config.set_schema_type("type");
auto property = type_config.add_properties();
property->set_property_name("property");
property->set_data_type(TYPE_STRING);
property->set_cardinality(CARDINALITY_REQUIRED);
property->mutable_string_indexing_config()->set_term_match_type(
TERM_MATCH_EXACT);
SchemaUtil::TypeConfigMap type_config_map;
type_config_map.emplace("type", type_config);
// Create an empty schema type mapper
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<KeyMapper<SchemaTypeId>> schema_type_mapper,
DynamicTrieKeyMapper<SchemaTypeId>::Create(
filesystem_, test_dir_ + "/schema_type_mapper",
/*maximum_size_bytes=*/3 * 128 * 1024));
EXPECT_THAT(
SchemaTypeManager::Create(type_config_map, schema_type_mapper.get()),
StatusIs(libtextclassifier3::StatusCode::NOT_FOUND));
}
} // namespace
} // namespace lib
} // namespace icing