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