1 // Copyright (C) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "icing/schema/schema-util.h"
16
17 #include <cstdint>
18 #include <string>
19 #include <string_view>
20 #include <unordered_map>
21 #include <unordered_set>
22 #include <utility>
23
24 #include "icing/text_classifier/lib3/utils/base/status.h"
25 #include "icing/absl_ports/annotate.h"
26 #include "icing/absl_ports/canonical_errors.h"
27 #include "icing/absl_ports/str_cat.h"
28 #include "icing/absl_ports/str_join.h"
29 #include "icing/legacy/core/icing-string-util.h"
30 #include "icing/proto/schema.pb.h"
31 #include "icing/proto/term.pb.h"
32 #include "icing/util/logging.h"
33 #include "icing/util/status-macros.h"
34
35 namespace icing {
36 namespace lib {
37
38 namespace {
39
ArePropertiesEqual(const PropertyConfigProto & old_property,const PropertyConfigProto & new_property)40 bool ArePropertiesEqual(const PropertyConfigProto& old_property,
41 const PropertyConfigProto& new_property) {
42 return old_property.property_name() == new_property.property_name() &&
43 old_property.data_type() == new_property.data_type() &&
44 old_property.schema_type() == new_property.schema_type() &&
45 old_property.cardinality() == new_property.cardinality() &&
46 old_property.string_indexing_config().term_match_type() ==
47 new_property.string_indexing_config().term_match_type() &&
48 old_property.string_indexing_config().tokenizer_type() ==
49 new_property.string_indexing_config().tokenizer_type() &&
50 old_property.document_indexing_config().index_nested_properties() ==
51 new_property.document_indexing_config().index_nested_properties();
52 }
53
IsCardinalityCompatible(const PropertyConfigProto & old_property,const PropertyConfigProto & new_property)54 bool IsCardinalityCompatible(const PropertyConfigProto& old_property,
55 const PropertyConfigProto& new_property) {
56 if (old_property.cardinality() < new_property.cardinality()) {
57 // We allow a new, less restrictive cardinality (i.e. a REQUIRED field
58 // can become REPEATED or OPTIONAL, but not the other way around).
59 ICING_VLOG(1) << absl_ports::StrCat(
60 "Cardinality is more restrictive than before ",
61 PropertyConfigProto::Cardinality::Code_Name(old_property.cardinality()),
62 "->",
63 PropertyConfigProto::Cardinality::Code_Name(
64 new_property.cardinality()));
65 return false;
66 }
67 return true;
68 }
69
IsDataTypeCompatible(const PropertyConfigProto & old_property,const PropertyConfigProto & new_property)70 bool IsDataTypeCompatible(const PropertyConfigProto& old_property,
71 const PropertyConfigProto& new_property) {
72 if (old_property.data_type() != new_property.data_type()) {
73 // TODO(cassiewang): Maybe we can be a bit looser with this, e.g. we just
74 // string cast an int64_t to a string. But for now, we'll stick with
75 // simplistics.
76 ICING_VLOG(1) << absl_ports::StrCat(
77 "Data type ",
78 PropertyConfigProto::DataType::Code_Name(old_property.data_type()),
79 "->",
80 PropertyConfigProto::DataType::Code_Name(new_property.data_type()));
81 return false;
82 }
83 return true;
84 }
85
IsSchemaTypeCompatible(const PropertyConfigProto & old_property,const PropertyConfigProto & new_property)86 bool IsSchemaTypeCompatible(const PropertyConfigProto& old_property,
87 const PropertyConfigProto& new_property) {
88 if (old_property.schema_type() != new_property.schema_type()) {
89 ICING_VLOG(1) << absl_ports::StrCat("Schema type ",
90 old_property.schema_type(), "->",
91 new_property.schema_type());
92 return false;
93 }
94 return true;
95 }
96
IsPropertyCompatible(const PropertyConfigProto & old_property,const PropertyConfigProto & new_property)97 bool IsPropertyCompatible(const PropertyConfigProto& old_property,
98 const PropertyConfigProto& new_property) {
99 return IsDataTypeCompatible(old_property, new_property) &&
100 IsSchemaTypeCompatible(old_property, new_property) &&
101 IsCardinalityCompatible(old_property, new_property);
102 }
103
IsTermMatchTypeCompatible(const StringIndexingConfig & old_indexed,const StringIndexingConfig & new_indexed)104 bool IsTermMatchTypeCompatible(const StringIndexingConfig& old_indexed,
105 const StringIndexingConfig& new_indexed) {
106 return old_indexed.term_match_type() == new_indexed.term_match_type() &&
107 old_indexed.tokenizer_type() == new_indexed.tokenizer_type();
108 }
109
AddIncompatibleChangeToDelta(std::unordered_set<std::string> & incompatible_delta,const SchemaTypeConfigProto & old_type_config,const SchemaUtil::DependencyMap & new_schema_dependency_map,const SchemaUtil::TypeConfigMap & old_type_config_map,const SchemaUtil::TypeConfigMap & new_type_config_map)110 void AddIncompatibleChangeToDelta(
111 std::unordered_set<std::string>& incompatible_delta,
112 const SchemaTypeConfigProto& old_type_config,
113 const SchemaUtil::DependencyMap& new_schema_dependency_map,
114 const SchemaUtil::TypeConfigMap& old_type_config_map,
115 const SchemaUtil::TypeConfigMap& new_type_config_map) {
116 // If this type is incompatible, then every type that depends on it might
117 // also be incompatible. Use the dependency map to mark those ones as
118 // incompatible too.
119 incompatible_delta.insert(old_type_config.schema_type());
120 auto parent_types_itr =
121 new_schema_dependency_map.find(old_type_config.schema_type());
122 if (parent_types_itr != new_schema_dependency_map.end()) {
123 for (std::string_view parent_type : parent_types_itr->second) {
124 // The types from new_schema that depend on the current
125 // old_type_config may not present in old_schema.
126 // Those types will be listed at schema_delta.schema_types_new
127 // instead.
128 std::string parent_type_str(parent_type);
129 if (old_type_config_map.find(parent_type_str) !=
130 old_type_config_map.end()) {
131 incompatible_delta.insert(std::move(parent_type_str));
132 }
133 }
134 }
135 }
136
137 } // namespace
138
ExpandTranstiveDependencies(const SchemaUtil::DependencyMap & child_to_direct_parent_map,std::string_view type,SchemaUtil::DependencyMap * expanded_child_to_parent_map,std::unordered_set<std::string_view> * pending_expansions,std::unordered_set<std::string_view> * orphaned_types)139 libtextclassifier3::Status ExpandTranstiveDependencies(
140 const SchemaUtil::DependencyMap& child_to_direct_parent_map,
141 std::string_view type,
142 SchemaUtil::DependencyMap* expanded_child_to_parent_map,
143 std::unordered_set<std::string_view>* pending_expansions,
144 std::unordered_set<std::string_view>* orphaned_types) {
145 auto expanded_itr = expanded_child_to_parent_map->find(type);
146 if (expanded_itr != expanded_child_to_parent_map->end()) {
147 // We've already expanded this type. Just return.
148 return libtextclassifier3::Status::OK;
149 }
150 auto itr = child_to_direct_parent_map.find(type);
151 if (itr == child_to_direct_parent_map.end()) {
152 // It's an orphan. Just return.
153 orphaned_types->insert(type);
154 return libtextclassifier3::Status::OK;
155 }
156 pending_expansions->insert(type);
157 std::unordered_set<std::string_view> expanded_dependencies;
158
159 // Add all of the direct parent dependencies.
160 expanded_dependencies.reserve(itr->second.size());
161 expanded_dependencies.insert(itr->second.begin(), itr->second.end());
162
163 // Iterate through each direct parent and add their indirect parents.
164 for (std::string_view dep : itr->second) {
165 // 1. Check if we're in the middle of expanding this type - IOW there's a
166 // cycle!
167 if (pending_expansions->count(dep) > 0) {
168 return absl_ports::InvalidArgumentError(
169 absl_ports::StrCat("Infinite loop detected in type configs. '", type,
170 "' references itself."));
171 }
172
173 // 2. Expand this type as needed.
174 ICING_RETURN_IF_ERROR(ExpandTranstiveDependencies(
175 child_to_direct_parent_map, dep, expanded_child_to_parent_map,
176 pending_expansions, orphaned_types));
177 if (orphaned_types->count(dep) > 0) {
178 // Dep is an orphan. Just skip to the next dep.
179 continue;
180 }
181
182 // 3. Dep has been fully expanded. Add all of its dependencies to this
183 // type's dependencies.
184 auto dep_expanded_itr = expanded_child_to_parent_map->find(dep);
185 expanded_dependencies.reserve(expanded_dependencies.size() +
186 dep_expanded_itr->second.size());
187 expanded_dependencies.insert(dep_expanded_itr->second.begin(),
188 dep_expanded_itr->second.end());
189 }
190 expanded_child_to_parent_map->insert(
191 {type, std::move(expanded_dependencies)});
192 pending_expansions->erase(type);
193 return libtextclassifier3::Status::OK;
194 }
195
196 // Expands the dependencies represented by the child_to_direct_parent_map to
197 // also include indirect parents.
198 //
199 // Ex. Suppose we have a schema with four types A, B, C, D. A has a property of
200 // type B and B has a property of type C. C and D only have non-document
201 // properties.
202 //
203 // The child to direct parent dependency map for this schema would be:
204 // C -> B
205 // B -> A
206 //
207 // This function would expand it so that A is also present as an indirect parent
208 // of C.
209 libtextclassifier3::StatusOr<SchemaUtil::DependencyMap>
ExpandTranstiveDependencies(const SchemaUtil::DependencyMap & child_to_direct_parent_map)210 ExpandTranstiveDependencies(
211 const SchemaUtil::DependencyMap& child_to_direct_parent_map) {
212 SchemaUtil::DependencyMap expanded_child_to_parent_map;
213
214 // Types that we are expanding.
215 std::unordered_set<std::string_view> pending_expansions;
216
217 // Types that have no parents that depend on them.
218 std::unordered_set<std::string_view> orphaned_types;
219 for (const auto& kvp : child_to_direct_parent_map) {
220 ICING_RETURN_IF_ERROR(ExpandTranstiveDependencies(
221 child_to_direct_parent_map, kvp.first, &expanded_child_to_parent_map,
222 &pending_expansions, &orphaned_types));
223 }
224 return expanded_child_to_parent_map;
225 }
226
227 // Builds a transitive child-parent dependency map. 'Orphaned' types (types with
228 // no parents) will not be present in the map.
229 //
230 // Ex. Suppose we have a schema with four types A, B, C, D. A has a property of
231 // type B and B has a property of type C. C and D only have non-document
232 // properties.
233 //
234 // The transitive child-parent dependency map for this schema would be:
235 // C -> A, B
236 // B -> A
237 //
238 // A and D would be considered orphaned properties because no type refers to
239 // them.
240 //
241 // RETURNS:
242 // On success, a transitive child-parent dependency map of all types in the
243 // schema.
244 // INVALID_ARGUMENT if the schema contains a cycle or an undefined type.
245 // ALREADY_EXISTS if a schema type is specified more than once in the schema
246 libtextclassifier3::StatusOr<SchemaUtil::DependencyMap>
BuildTransitiveDependencyGraph(const SchemaProto & schema)247 BuildTransitiveDependencyGraph(const SchemaProto& schema) {
248 // Child to parent map.
249 SchemaUtil::DependencyMap child_to_direct_parent_map;
250
251 // Add all first-order dependencies.
252 std::unordered_set<std::string_view> known_types;
253 std::unordered_set<std::string_view> unknown_types;
254 for (const auto& type_config : schema.types()) {
255 std::string_view schema_type(type_config.schema_type());
256 if (known_types.count(schema_type) > 0) {
257 return absl_ports::AlreadyExistsError(absl_ports::StrCat(
258 "Field 'schema_type' '", schema_type, "' is already defined"));
259 }
260 known_types.insert(schema_type);
261 unknown_types.erase(schema_type);
262 for (const auto& property_config : type_config.properties()) {
263 if (property_config.data_type() ==
264 PropertyConfigProto::DataType::DOCUMENT) {
265 // Need to know what schema_type these Document properties should be
266 // validated against
267 std::string_view property_schema_type(property_config.schema_type());
268 if (property_schema_type == schema_type) {
269 return absl_ports::InvalidArgumentError(
270 absl_ports::StrCat("Infinite loop detected in type configs. '",
271 schema_type, "' references itself."));
272 }
273 if (known_types.count(property_schema_type) == 0) {
274 unknown_types.insert(property_schema_type);
275 }
276 auto itr = child_to_direct_parent_map.find(property_schema_type);
277 if (itr == child_to_direct_parent_map.end()) {
278 child_to_direct_parent_map.insert(
279 {property_schema_type, std::unordered_set<std::string_view>()});
280 itr = child_to_direct_parent_map.find(property_schema_type);
281 }
282 itr->second.insert(schema_type);
283 }
284 }
285 }
286 if (!unknown_types.empty()) {
287 return absl_ports::InvalidArgumentError(absl_ports::StrCat(
288 "Undefined 'schema_type's: ", absl_ports::StrJoin(unknown_types, ",")));
289 }
290 return ExpandTranstiveDependencies(child_to_direct_parent_map);
291 }
292
Validate(const SchemaProto & schema)293 libtextclassifier3::StatusOr<SchemaUtil::DependencyMap> SchemaUtil::Validate(
294 const SchemaProto& schema) {
295 // 1. Build the dependency map. This will detect any cycles, non-existent or
296 // duplicate types in the schema.
297 ICING_ASSIGN_OR_RETURN(SchemaUtil::DependencyMap dependency_map,
298 BuildTransitiveDependencyGraph(schema));
299
300 // Tracks PropertyConfigs within a SchemaTypeConfig that we've validated
301 // already.
302 std::unordered_set<std::string_view> known_property_names;
303
304 // 2. Validate the properties of each type.
305 for (const auto& type_config : schema.types()) {
306 std::string_view schema_type(type_config.schema_type());
307 ICING_RETURN_IF_ERROR(ValidateSchemaType(schema_type));
308
309 // We only care about properties being unique within one type_config
310 known_property_names.clear();
311
312 for (const auto& property_config : type_config.properties()) {
313 std::string_view property_name(property_config.property_name());
314 ICING_RETURN_IF_ERROR(ValidatePropertyName(property_name, schema_type));
315
316 // Property names must be unique
317 if (!known_property_names.insert(property_name).second) {
318 return absl_ports::AlreadyExistsError(absl_ports::StrCat(
319 "Field 'property_name' '", property_name,
320 "' is already defined for schema '", schema_type, "'"));
321 }
322
323 auto data_type = property_config.data_type();
324 ICING_RETURN_IF_ERROR(
325 ValidateDataType(data_type, schema_type, property_name));
326
327 if (data_type == PropertyConfigProto::DataType::DOCUMENT) {
328 // Need to know what schema_type these Document properties should be
329 // validated against
330 std::string_view property_schema_type(property_config.schema_type());
331 libtextclassifier3::Status validated_status =
332 ValidateSchemaType(property_schema_type);
333 if (!validated_status.ok()) {
334 return absl_ports::Annotate(
335 validated_status,
336 absl_ports::StrCat("Field 'schema_type' is required for DOCUMENT "
337 "data_types in schema property '",
338 schema_type, ".", property_name, "'"));
339 }
340 }
341
342 ICING_RETURN_IF_ERROR(ValidateCardinality(property_config.cardinality(),
343 schema_type, property_name));
344
345 if (data_type == PropertyConfigProto::DataType::STRING) {
346 ICING_RETURN_IF_ERROR(ValidateStringIndexingConfig(
347 property_config.string_indexing_config(), data_type, schema_type,
348 property_name));
349 }
350 }
351 }
352
353 return dependency_map;
354 }
355
ValidateSchemaType(std::string_view schema_type)356 libtextclassifier3::Status SchemaUtil::ValidateSchemaType(
357 std::string_view schema_type) {
358 // Require a schema_type
359 if (schema_type.empty()) {
360 return absl_ports::InvalidArgumentError(
361 "Field 'schema_type' cannot be empty.");
362 }
363
364 return libtextclassifier3::Status::OK;
365 }
366
ValidatePropertyName(std::string_view property_name,std::string_view schema_type)367 libtextclassifier3::Status SchemaUtil::ValidatePropertyName(
368 std::string_view property_name, std::string_view schema_type) {
369 // Require a property_name
370 if (property_name.empty()) {
371 return absl_ports::InvalidArgumentError(
372 absl_ports::StrCat("Field 'property_name' for schema '", schema_type,
373 "' cannot be empty."));
374 }
375
376 // Only support alphanumeric values.
377 for (char c : property_name) {
378 if (!std::isalnum(c)) {
379 return absl_ports::InvalidArgumentError(
380 absl_ports::StrCat("Field 'property_name' '", property_name,
381 "' can only contain alphanumeric characters."));
382 }
383 }
384
385 return libtextclassifier3::Status::OK;
386 }
387
ValidateDataType(PropertyConfigProto::DataType::Code data_type,std::string_view schema_type,std::string_view property_name)388 libtextclassifier3::Status SchemaUtil::ValidateDataType(
389 PropertyConfigProto::DataType::Code data_type, std::string_view schema_type,
390 std::string_view property_name) {
391 // UNKNOWN is the default enum value and should only be used for backwards
392 // compatibility
393 if (data_type == PropertyConfigProto::DataType::UNKNOWN) {
394 return absl_ports::InvalidArgumentError(absl_ports::StrCat(
395 "Field 'data_type' cannot be UNKNOWN for schema property '",
396 schema_type, ".", property_name, "'"));
397 }
398
399 return libtextclassifier3::Status::OK;
400 }
401
ValidateCardinality(PropertyConfigProto::Cardinality::Code cardinality,std::string_view schema_type,std::string_view property_name)402 libtextclassifier3::Status SchemaUtil::ValidateCardinality(
403 PropertyConfigProto::Cardinality::Code cardinality,
404 std::string_view schema_type, std::string_view property_name) {
405 // UNKNOWN is the default enum value and should only be used for backwards
406 // compatibility
407 if (cardinality == PropertyConfigProto::Cardinality::UNKNOWN) {
408 return absl_ports::InvalidArgumentError(absl_ports::StrCat(
409 "Field 'cardinality' cannot be UNKNOWN for schema property '",
410 schema_type, ".", property_name, "'"));
411 }
412
413 return libtextclassifier3::Status::OK;
414 }
415
ValidateStringIndexingConfig(const StringIndexingConfig & config,PropertyConfigProto::DataType::Code data_type,std::string_view schema_type,std::string_view property_name)416 libtextclassifier3::Status SchemaUtil::ValidateStringIndexingConfig(
417 const StringIndexingConfig& config,
418 PropertyConfigProto::DataType::Code data_type, std::string_view schema_type,
419 std::string_view property_name) {
420 if (config.term_match_type() == TermMatchType::UNKNOWN &&
421 config.tokenizer_type() != StringIndexingConfig::TokenizerType::NONE) {
422 // They set a tokenizer type, but no term match type.
423 return absl_ports::InvalidArgumentError(absl_ports::StrCat(
424 "Indexed string property '", schema_type, ".", property_name,
425 "' cannot have a term match type UNKNOWN"));
426 }
427
428 if (config.term_match_type() != TermMatchType::UNKNOWN &&
429 config.tokenizer_type() == StringIndexingConfig::TokenizerType::NONE) {
430 // They set a term match type, but no tokenizer type
431 return absl_ports::InvalidArgumentError(
432 absl_ports::StrCat("Indexed string property '", property_name,
433 "' cannot have a tokenizer type of NONE"));
434 }
435
436 return libtextclassifier3::Status::OK;
437 }
438
BuildTypeConfigMap(const SchemaProto & schema,SchemaUtil::TypeConfigMap * type_config_map)439 void SchemaUtil::BuildTypeConfigMap(
440 const SchemaProto& schema, SchemaUtil::TypeConfigMap* type_config_map) {
441 type_config_map->clear();
442 for (const SchemaTypeConfigProto& type_config : schema.types()) {
443 type_config_map->emplace(type_config.schema_type(), type_config);
444 }
445 }
446
ParsePropertyConfigs(const SchemaTypeConfigProto & type_config)447 SchemaUtil::ParsedPropertyConfigs SchemaUtil::ParsePropertyConfigs(
448 const SchemaTypeConfigProto& type_config) {
449 ParsedPropertyConfigs parsed_property_configs;
450
451 // TODO(cassiewang): consider caching property_config_map for some properties,
452 // e.g. using LRU cache. Or changing schema.proto to use go/protomap.
453 for (const PropertyConfigProto& property_config : type_config.properties()) {
454 parsed_property_configs.property_config_map.emplace(
455 property_config.property_name(), &property_config);
456 if (property_config.cardinality() ==
457 PropertyConfigProto::Cardinality::REQUIRED) {
458 parsed_property_configs.num_required_properties++;
459 }
460
461 // A non-default term_match_type indicates that this property is meant to be
462 // indexed.
463 if (property_config.string_indexing_config().term_match_type() !=
464 TermMatchType::UNKNOWN) {
465 parsed_property_configs.num_indexed_properties++;
466 }
467 }
468
469 return parsed_property_configs;
470 }
471
ComputeCompatibilityDelta(const SchemaProto & old_schema,const SchemaProto & new_schema,const DependencyMap & new_schema_dependency_map)472 const SchemaUtil::SchemaDelta SchemaUtil::ComputeCompatibilityDelta(
473 const SchemaProto& old_schema, const SchemaProto& new_schema,
474 const DependencyMap& new_schema_dependency_map) {
475 SchemaDelta schema_delta;
476
477 TypeConfigMap old_type_config_map, new_type_config_map;
478 BuildTypeConfigMap(old_schema, &old_type_config_map);
479 BuildTypeConfigMap(new_schema, &new_type_config_map);
480
481 // Iterate through and check each field of the old schema
482 for (const auto& old_type_config : old_schema.types()) {
483 auto new_schema_type_and_config =
484 new_type_config_map.find(old_type_config.schema_type());
485
486 if (new_schema_type_and_config == new_type_config_map.end()) {
487 // Didn't find the old schema type in the new schema, all the old
488 // documents of this schema type are invalid without the schema
489 ICING_VLOG(1) << absl_ports::StrCat("Previously defined schema type '",
490 old_type_config.schema_type(),
491 "' was not defined in new schema");
492 schema_delta.schema_types_deleted.insert(old_type_config.schema_type());
493 continue;
494 }
495
496 ParsedPropertyConfigs new_parsed_property_configs =
497 ParsePropertyConfigs(new_schema_type_and_config->second);
498
499 // We only need to check the old, existing properties to see if they're
500 // compatible since we'll have old data that may be invalidated or need to
501 // be reindexed.
502 int32_t old_required_properties = 0;
503 int32_t old_indexed_properties = 0;
504
505 // If there is a different number of properties, then there must have been a
506 // change.
507 bool has_property_changed =
508 old_type_config.properties_size() !=
509 new_schema_type_and_config->second.properties_size();
510 bool is_incompatible = false;
511 bool is_index_incompatible = false;
512 for (const auto& old_property_config : old_type_config.properties()) {
513 if (old_property_config.cardinality() ==
514 PropertyConfigProto::Cardinality::REQUIRED) {
515 ++old_required_properties;
516 }
517
518 // A non-default term_match_type indicates that this property is meant to
519 // be indexed.
520 bool is_indexed_property =
521 old_property_config.string_indexing_config().term_match_type() !=
522 TermMatchType::UNKNOWN;
523 if (is_indexed_property) {
524 ++old_indexed_properties;
525 }
526
527 auto new_property_name_and_config =
528 new_parsed_property_configs.property_config_map.find(
529 old_property_config.property_name());
530
531 if (new_property_name_and_config ==
532 new_parsed_property_configs.property_config_map.end()) {
533 // Didn't find the old property
534 ICING_VLOG(1) << absl_ports::StrCat(
535 "Previously defined property type '", old_type_config.schema_type(),
536 ".", old_property_config.property_name(),
537 "' was not defined in new schema");
538 is_incompatible = true;
539 is_index_incompatible |= is_indexed_property;
540 continue;
541 }
542
543 const PropertyConfigProto* new_property_config =
544 new_property_name_and_config->second;
545 if (!has_property_changed &&
546 !ArePropertiesEqual(old_property_config, *new_property_config)) {
547 // Finally found a property that changed.
548 has_property_changed = true;
549 }
550
551 if (!IsPropertyCompatible(old_property_config, *new_property_config)) {
552 ICING_VLOG(1) << absl_ports::StrCat(
553 "Property '", old_type_config.schema_type(), ".",
554 old_property_config.property_name(), "' is incompatible.");
555 is_incompatible = true;
556 }
557
558 // Any change in the indexed property requires a reindexing
559 if (!IsTermMatchTypeCompatible(
560 old_property_config.string_indexing_config(),
561 new_property_config->string_indexing_config()) ||
562 old_property_config.document_indexing_config()
563 .index_nested_properties() !=
564 new_property_config->document_indexing_config()
565 .index_nested_properties()) {
566 is_index_incompatible = true;
567 }
568 }
569
570 // We can't have new properties that are REQUIRED since we won't know how
571 // to backfill the data, and the existing data will be invalid. We're
572 // guaranteed from our previous checks that all the old properties are also
573 // present in the new property config, so we can do a simple int comparison
574 // here to detect new required properties.
575 if (new_parsed_property_configs.num_required_properties >
576 old_required_properties) {
577 ICING_VLOG(1) << absl_ports::StrCat(
578 "New schema '", old_type_config.schema_type(),
579 "' has REQUIRED properties that are not "
580 "present in the previously defined schema");
581 is_incompatible = true;
582 }
583
584 // If we've gained any new indexed properties, then the section ids may
585 // change. Since the section ids are stored in the index, we'll need to
586 // reindex everything.
587 if (new_parsed_property_configs.num_indexed_properties >
588 old_indexed_properties) {
589 ICING_VLOG(1) << absl_ports::StrCat(
590 "Set of indexed properties in schema type '",
591 old_type_config.schema_type(),
592 "' has changed, required reindexing.");
593 is_index_incompatible = true;
594 }
595
596 if (is_incompatible) {
597 AddIncompatibleChangeToDelta(schema_delta.schema_types_incompatible,
598 old_type_config, new_schema_dependency_map,
599 old_type_config_map, new_type_config_map);
600 }
601
602 if (is_index_incompatible) {
603 AddIncompatibleChangeToDelta(schema_delta.schema_types_index_incompatible,
604 old_type_config, new_schema_dependency_map,
605 old_type_config_map, new_type_config_map);
606 }
607
608 if (!is_incompatible && !is_index_incompatible && has_property_changed) {
609 schema_delta.schema_types_changed_fully_compatible.insert(
610 old_type_config.schema_type());
611 }
612
613 // Lastly, remove this type from the map. We know that this type can't
614 // come up in future iterations through the old schema types because the old
615 // type config has unique types.
616 new_type_config_map.erase(old_type_config.schema_type());
617 }
618
619 // Any types that are still present in the new_type_config_map are newly added
620 // types.
621 schema_delta.schema_types_new.reserve(new_type_config_map.size());
622 for (auto& kvp : new_type_config_map) {
623 schema_delta.schema_types_new.insert(std::move(kvp.first));
624 }
625
626 return schema_delta;
627 }
628
629 } // namespace lib
630 } // namespace icing
631