blob: d0a0554c1445ed62e7f6ac99772a0efea4a929a6 [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/backup-schema-producer.h"
#include <string_view>
#include <unordered_map>
#include <vector>
#include "icing/proto/schema.pb.h"
#include "icing/proto/term.pb.h"
#include "icing/schema/property-util.h"
#include "icing/schema/section.h"
#include "icing/util/status-macros.h"
namespace icing {
namespace lib {
namespace {
// Creates a map of property to indexed id count based on the list of indexed
// properties provided by metadata_list.
// For all non-document properties, the value will always be 1.
// For document properties, the value will be the number of nested properties
// that are indexed with that document type.
std::unordered_map<std::string_view, int> CreateIndexedIdCountMap(
const std::vector<SectionMetadata>* metadata_list) {
std::unordered_map<std::string_view, int> property_indexed_id_count_map;
for (const SectionMetadata& metadata : *metadata_list) {
std::string_view top_level_property;
size_t separator_pos =
metadata.path.find(property_util::kPropertyPathSeparator);
if (separator_pos == std::string::npos) {
top_level_property = metadata.path;
} else {
top_level_property =
std::string_view(metadata.path.c_str(), separator_pos);
}
int& count = property_indexed_id_count_map[top_level_property];
++count;
}
return property_indexed_id_count_map;
}
// Returns the indices (within schema.types()) of all types that are rollback
// incompatible (old code cannot handle these types if they are unmodified).
//
// Currently, this means types that:
// 1. Use RFC822 tokenization for any properties
// 2. Use more than 16 indexed properties
libtextclassifier3::StatusOr<std::vector<int>>
GetRollbackIncompatibleTypeIndices(const SchemaProto& schema,
const SectionManager& type_manager) {
std::vector<int> invalid_type_indices;
for (int i = 0; i < schema.types_size(); ++i) {
const SchemaTypeConfigProto& type = schema.types(i);
bool rollback_incompatible = false;
for (const PropertyConfigProto& property : type.properties()) {
if (property.string_indexing_config().tokenizer_type() ==
StringIndexingConfig::TokenizerType::RFC822) {
rollback_incompatible = true;
break;
}
}
if (rollback_incompatible) {
invalid_type_indices.push_back(i);
continue;
}
ICING_ASSIGN_OR_RETURN(const std::vector<SectionMetadata>* metadata_list,
type_manager.GetMetadataList(type.schema_type()));
if (metadata_list->size() > kOldTotalNumSections) {
invalid_type_indices.push_back(i);
}
}
return invalid_type_indices;
}
} // namespace
/* static */ libtextclassifier3::StatusOr<BackupSchemaProducer>
BackupSchemaProducer::Create(const SchemaProto& schema,
const SectionManager& type_manager) {
ICING_ASSIGN_OR_RETURN(
std::vector<int> invalid_type_indices,
GetRollbackIncompatibleTypeIndices(schema, type_manager));
if (invalid_type_indices.empty()) {
return BackupSchemaProducer();
}
SchemaProto backup_schema(schema);
std::unordered_map<std::string_view, int> type_indexed_property_count;
for (int i : invalid_type_indices) {
SchemaTypeConfigProto* type = backup_schema.mutable_types(i);
// This should never cause an error - every type should have an entry in the
// type_manager.
ICING_ASSIGN_OR_RETURN(const std::vector<SectionMetadata>* metadata_list,
type_manager.GetMetadataList(type->schema_type()));
int num_indexed_sections = metadata_list->size();
std::unordered_map<std::string_view, int> property_indexed_id_count_map;
if (num_indexed_sections > kOldTotalNumSections) {
property_indexed_id_count_map = CreateIndexedIdCountMap(metadata_list);
}
// Step 1. Switch all properties with RFC tokenizer as unindexed.
for (PropertyConfigProto& property : *type->mutable_properties()) {
// If the property uses the RFC tokenizer, then we need to set it to NONE
// and set match type UNKNOWN.
if (property.string_indexing_config().tokenizer_type() ==
StringIndexingConfig::TokenizerType::RFC822) {
property.clear_string_indexing_config();
--num_indexed_sections;
property_indexed_id_count_map.erase(property.property_name());
}
}
// Step 2. If there are any types that exceed the old indexed property
// limit, then mark indexed properties as unindexed until we're back under
// the limit.
if (num_indexed_sections <= kOldTotalNumSections) {
continue;
}
// We expect that the last properties were the ones added most recently and
// are the least crucial, so we do removal in reverse order. This is a bit
// arbitrary, but we don't really have sufficient information to make this
// judgment anyways.
for (auto itr = type->mutable_properties()->rbegin();
itr != type->mutable_properties()->rend(); ++itr) {
auto indexed_count_itr =
property_indexed_id_count_map.find(itr->property_name());
if (indexed_count_itr == property_indexed_id_count_map.end()) {
continue;
}
// Mark this property as unindexed and subtract all indexed property ids
// consumed by this property.
PropertyConfigProto& property = *itr;
property.clear_document_indexing_config();
property.clear_string_indexing_config();
property.clear_integer_indexing_config();
num_indexed_sections -= indexed_count_itr->second;
if (num_indexed_sections <= kOldTotalNumSections) {
break;
}
}
}
return BackupSchemaProducer(std::move(backup_schema));
}
} // namespace lib
} // namespace icing