1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.appsearch.external.localstorage.converter; 18 19 import android.annotation.NonNull; 20 import android.app.appsearch.AppSearchSchema; 21 import android.util.Log; 22 23 import com.google.android.icing.proto.DocumentIndexingConfig; 24 import com.google.android.icing.proto.EmbeddingIndexingConfig; 25 import com.google.android.icing.proto.IntegerIndexingConfig; 26 import com.google.android.icing.proto.JoinableConfig; 27 import com.google.android.icing.proto.PropertyConfigProto; 28 import com.google.android.icing.proto.SchemaTypeConfigProto; 29 import com.google.android.icing.proto.SchemaTypeConfigProtoOrBuilder; 30 import com.google.android.icing.proto.StringIndexingConfig; 31 import com.google.android.icing.proto.TermMatchType; 32 33 import java.util.List; 34 import java.util.Objects; 35 36 /** 37 * Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}. 38 * 39 * @hide 40 */ 41 public final class SchemaToProtoConverter { 42 private static final String TAG = "AppSearchSchemaToProtoC"; 43 SchemaToProtoConverter()44 private SchemaToProtoConverter() {} 45 46 /** 47 * Converts an {@link android.app.appsearch.AppSearchSchema} into a {@link 48 * SchemaTypeConfigProto}. 49 */ 50 // TODO(b/284356266): Consider handling addition of schema name prefixes in this function. 51 @NonNull toSchemaTypeConfigProto( @onNull AppSearchSchema schema, int version)52 public static SchemaTypeConfigProto toSchemaTypeConfigProto( 53 @NonNull AppSearchSchema schema, int version) { 54 Objects.requireNonNull(schema); 55 SchemaTypeConfigProto.Builder protoBuilder = 56 SchemaTypeConfigProto.newBuilder() 57 .setSchemaType(schema.getSchemaType()) 58 .setDescription(schema.getDescription()) 59 .setVersion(version); 60 List<AppSearchSchema.PropertyConfig> properties = schema.getProperties(); 61 for (int i = 0; i < properties.size(); i++) { 62 PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i)); 63 protoBuilder.addProperties(propertyProto); 64 } 65 protoBuilder.addAllParentTypes(schema.getParentTypes()); 66 return protoBuilder.build(); 67 } 68 69 @NonNull toPropertyConfigProto( @onNull AppSearchSchema.PropertyConfig property)70 private static PropertyConfigProto toPropertyConfigProto( 71 @NonNull AppSearchSchema.PropertyConfig property) { 72 Objects.requireNonNull(property); 73 PropertyConfigProto.Builder builder = 74 PropertyConfigProto.newBuilder() 75 .setPropertyName(property.getName()) 76 .setDescription(property.getDescription()); 77 78 // Set dataType 79 @AppSearchSchema.PropertyConfig.DataType int dataType = property.getDataType(); 80 PropertyConfigProto.DataType.Code dataTypeProto = 81 PropertyConfigProto.DataType.Code.forNumber(dataType); 82 if (dataTypeProto == null) { 83 throw new IllegalArgumentException("Invalid dataType: " + dataType); 84 } 85 builder.setDataType(dataTypeProto); 86 87 // Set cardinality 88 @AppSearchSchema.PropertyConfig.Cardinality int cardinality = property.getCardinality(); 89 PropertyConfigProto.Cardinality.Code cardinalityProto = 90 PropertyConfigProto.Cardinality.Code.forNumber(cardinality); 91 if (cardinalityProto == null) { 92 throw new IllegalArgumentException("Invalid cardinality: " + dataType); 93 } 94 builder.setCardinality(cardinalityProto); 95 96 if (property instanceof AppSearchSchema.StringPropertyConfig) { 97 AppSearchSchema.StringPropertyConfig stringProperty = 98 (AppSearchSchema.StringPropertyConfig) property; 99 100 // Set JoinableConfig only if it is joinable (i.e. joinableValueType is not NONE). 101 if (stringProperty.getJoinableValueType() 102 != AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) { 103 JoinableConfig joinableConfig = 104 JoinableConfig.newBuilder() 105 .setValueType( 106 convertJoinableValueTypeToProto( 107 stringProperty.getJoinableValueType())) 108 .build(); 109 builder.setJoinableConfig(joinableConfig); 110 } 111 112 StringIndexingConfig stringIndexingConfig = 113 StringIndexingConfig.newBuilder() 114 .setTermMatchType( 115 convertTermMatchTypeToProto(stringProperty.getIndexingType())) 116 .setTokenizerType( 117 convertTokenizerTypeToProto(stringProperty.getTokenizerType())) 118 .build(); 119 builder.setStringIndexingConfig(stringIndexingConfig); 120 121 } else if (property instanceof AppSearchSchema.DocumentPropertyConfig) { 122 AppSearchSchema.DocumentPropertyConfig documentProperty = 123 (AppSearchSchema.DocumentPropertyConfig) property; 124 builder.setSchemaType(documentProperty.getSchemaType()) 125 .setDocumentIndexingConfig( 126 DocumentIndexingConfig.newBuilder() 127 .setIndexNestedProperties( 128 documentProperty.shouldIndexNestedProperties()) 129 .addAllIndexableNestedPropertiesList( 130 documentProperty.getIndexableNestedProperties())); 131 } else if (property instanceof AppSearchSchema.LongPropertyConfig) { 132 AppSearchSchema.LongPropertyConfig longProperty = 133 (AppSearchSchema.LongPropertyConfig) property; 134 // Set integer indexing config only if it is indexable (i.e. not INDEXING_TYPE_NONE). 135 if (longProperty.getIndexingType() 136 != AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE) { 137 IntegerIndexingConfig integerIndexingConfig = 138 IntegerIndexingConfig.newBuilder() 139 .setNumericMatchType( 140 convertNumericMatchTypeToProto( 141 longProperty.getIndexingType())) 142 .build(); 143 builder.setIntegerIndexingConfig(integerIndexingConfig); 144 } 145 } else if (property instanceof AppSearchSchema.EmbeddingPropertyConfig) { 146 AppSearchSchema.EmbeddingPropertyConfig embeddingProperty = 147 (AppSearchSchema.EmbeddingPropertyConfig) property; 148 // Set embedding indexing config only if it is indexable (i.e. not INDEXING_TYPE_NONE). 149 // Non-indexable embedding property only requires to builder.setDataType, without the 150 // need to set an EmbeddingIndexingConfig. 151 if (embeddingProperty.getIndexingType() 152 != AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE) { 153 EmbeddingIndexingConfig embeddingIndexingConfig = 154 EmbeddingIndexingConfig.newBuilder() 155 .setEmbeddingIndexingType( 156 convertEmbeddingIndexingTypeToProto( 157 embeddingProperty.getIndexingType())) 158 .build(); 159 builder.setEmbeddingIndexingConfig(embeddingIndexingConfig); 160 } 161 } 162 return builder.build(); 163 } 164 165 /** 166 * Converts a {@link SchemaTypeConfigProto} into an {@link 167 * android.app.appsearch.AppSearchSchema}. 168 */ 169 // TODO(b/284356266): Consider handling removal of schema name prefixes in this function. 170 @NonNull toAppSearchSchema(@onNull SchemaTypeConfigProtoOrBuilder proto)171 public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) { 172 Objects.requireNonNull(proto); 173 AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType()); 174 builder.setDescription(proto.getDescription()); 175 List<PropertyConfigProto> properties = proto.getPropertiesList(); 176 for (int i = 0; i < properties.size(); i++) { 177 AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i)); 178 builder.addProperty(propertyConfig); 179 } 180 List<String> parentTypes = proto.getParentTypesList(); 181 for (int i = 0; i < parentTypes.size(); i++) { 182 builder.addParentType(parentTypes.get(i)); 183 } 184 return builder.build(); 185 } 186 187 @NonNull toPropertyConfig( @onNull PropertyConfigProto proto)188 private static AppSearchSchema.PropertyConfig toPropertyConfig( 189 @NonNull PropertyConfigProto proto) { 190 Objects.requireNonNull(proto); 191 switch (proto.getDataType()) { 192 case STRING: 193 return toStringPropertyConfig(proto); 194 case INT64: 195 return toLongPropertyConfig(proto); 196 case DOUBLE: 197 return new AppSearchSchema.DoublePropertyConfig.Builder(proto.getPropertyName()) 198 .setDescription(proto.getDescription()) 199 .setCardinality(proto.getCardinality().getNumber()) 200 .build(); 201 case BOOLEAN: 202 return new AppSearchSchema.BooleanPropertyConfig.Builder(proto.getPropertyName()) 203 .setDescription(proto.getDescription()) 204 .setCardinality(proto.getCardinality().getNumber()) 205 .build(); 206 case BYTES: 207 return new AppSearchSchema.BytesPropertyConfig.Builder(proto.getPropertyName()) 208 .setDescription(proto.getDescription()) 209 .setCardinality(proto.getCardinality().getNumber()) 210 .build(); 211 case DOCUMENT: 212 return toDocumentPropertyConfig(proto); 213 case VECTOR: 214 return toEmbeddingPropertyConfig(proto); 215 default: 216 throw new IllegalArgumentException( 217 "Invalid dataType code: " + proto.getDataType().getNumber()); 218 } 219 } 220 221 @NonNull toStringPropertyConfig( @onNull PropertyConfigProto proto)222 private static AppSearchSchema.StringPropertyConfig toStringPropertyConfig( 223 @NonNull PropertyConfigProto proto) { 224 AppSearchSchema.StringPropertyConfig.Builder builder = 225 new AppSearchSchema.StringPropertyConfig.Builder(proto.getPropertyName()) 226 .setDescription(proto.getDescription()) 227 .setCardinality(proto.getCardinality().getNumber()) 228 .setJoinableValueType( 229 convertJoinableValueTypeFromProto( 230 proto.getJoinableConfig().getValueType())) 231 .setTokenizerType( 232 proto.getStringIndexingConfig().getTokenizerType().getNumber()); 233 234 // Set indexingType 235 TermMatchType.Code termMatchTypeProto = proto.getStringIndexingConfig().getTermMatchType(); 236 builder.setIndexingType(convertTermMatchTypeFromProto(termMatchTypeProto)); 237 238 return builder.build(); 239 } 240 241 @NonNull toDocumentPropertyConfig( @onNull PropertyConfigProto proto)242 private static AppSearchSchema.DocumentPropertyConfig toDocumentPropertyConfig( 243 @NonNull PropertyConfigProto proto) { 244 AppSearchSchema.DocumentPropertyConfig.Builder builder = 245 new AppSearchSchema.DocumentPropertyConfig.Builder( 246 proto.getPropertyName(), proto.getSchemaType()) 247 .setDescription(proto.getDescription()) 248 .setCardinality(proto.getCardinality().getNumber()) 249 .setShouldIndexNestedProperties( 250 proto.getDocumentIndexingConfig().getIndexNestedProperties()); 251 builder.addIndexableNestedProperties( 252 proto.getDocumentIndexingConfig().getIndexableNestedPropertiesListList()); 253 return builder.build(); 254 } 255 256 @NonNull toLongPropertyConfig( @onNull PropertyConfigProto proto)257 private static AppSearchSchema.LongPropertyConfig toLongPropertyConfig( 258 @NonNull PropertyConfigProto proto) { 259 AppSearchSchema.LongPropertyConfig.Builder builder = 260 new AppSearchSchema.LongPropertyConfig.Builder(proto.getPropertyName()) 261 .setDescription(proto.getDescription()) 262 .setCardinality(proto.getCardinality().getNumber()); 263 264 // Set indexingType 265 IntegerIndexingConfig.NumericMatchType.Code numericMatchTypeProto = 266 proto.getIntegerIndexingConfig().getNumericMatchType(); 267 builder.setIndexingType(convertNumericMatchTypeFromProto(numericMatchTypeProto)); 268 269 return builder.build(); 270 } 271 272 @NonNull toEmbeddingPropertyConfig( @onNull PropertyConfigProto proto)273 private static AppSearchSchema.EmbeddingPropertyConfig toEmbeddingPropertyConfig( 274 @NonNull PropertyConfigProto proto) { 275 AppSearchSchema.EmbeddingPropertyConfig.Builder builder = 276 new AppSearchSchema.EmbeddingPropertyConfig.Builder(proto.getPropertyName()) 277 .setCardinality(proto.getCardinality().getNumber()); 278 279 // Set indexingType 280 EmbeddingIndexingConfig.EmbeddingIndexingType.Code embeddingIndexingType = 281 proto.getEmbeddingIndexingConfig().getEmbeddingIndexingType(); 282 builder.setIndexingType(convertEmbeddingIndexingTypeFromProto(embeddingIndexingType)); 283 284 return builder.build(); 285 } 286 287 @NonNull convertJoinableValueTypeToProto( @ppSearchSchema.StringPropertyConfig.JoinableValueType int joinableValueType)288 private static JoinableConfig.ValueType.Code convertJoinableValueTypeToProto( 289 @AppSearchSchema.StringPropertyConfig.JoinableValueType int joinableValueType) { 290 switch (joinableValueType) { 291 case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE: 292 return JoinableConfig.ValueType.Code.NONE; 293 case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID: 294 return JoinableConfig.ValueType.Code.QUALIFIED_ID; 295 default: 296 throw new IllegalArgumentException( 297 "Invalid joinableValueType: " + joinableValueType); 298 } 299 } 300 301 @AppSearchSchema.StringPropertyConfig.JoinableValueType convertJoinableValueTypeFromProto( @onNull JoinableConfig.ValueType.Code joinableValueType)302 private static int convertJoinableValueTypeFromProto( 303 @NonNull JoinableConfig.ValueType.Code joinableValueType) { 304 switch (joinableValueType) { 305 case NONE: 306 return AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE; 307 case QUALIFIED_ID: 308 return AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID; 309 } 310 // Avoid crashing in the 'read' path; we should try to interpret the document to the 311 // extent possible. 312 Log.w(TAG, "Invalid joinableValueType: " + joinableValueType.getNumber()); 313 return AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE; 314 } 315 316 @NonNull convertTermMatchTypeToProto( @ppSearchSchema.StringPropertyConfig.IndexingType int indexingType)317 private static TermMatchType.Code convertTermMatchTypeToProto( 318 @AppSearchSchema.StringPropertyConfig.IndexingType int indexingType) { 319 switch (indexingType) { 320 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE: 321 return TermMatchType.Code.UNKNOWN; 322 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS: 323 return TermMatchType.Code.EXACT_ONLY; 324 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES: 325 return TermMatchType.Code.PREFIX; 326 default: 327 throw new IllegalArgumentException("Invalid indexingType: " + indexingType); 328 } 329 } 330 331 @AppSearchSchema.StringPropertyConfig.IndexingType convertTermMatchTypeFromProto(@onNull TermMatchType.Code termMatchType)332 private static int convertTermMatchTypeFromProto(@NonNull TermMatchType.Code termMatchType) { 333 switch (termMatchType) { 334 case UNKNOWN: 335 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE; 336 case EXACT_ONLY: 337 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS; 338 case PREFIX: 339 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES; 340 } 341 // Avoid crashing in the 'read' path; we should try to interpret the document to the 342 // extent possible. 343 Log.w(TAG, "Invalid indexingType: " + termMatchType.getNumber()); 344 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE; 345 } 346 347 @NonNull convertTokenizerTypeToProto( @ppSearchSchema.StringPropertyConfig.TokenizerType int tokenizerType)348 private static StringIndexingConfig.TokenizerType.Code convertTokenizerTypeToProto( 349 @AppSearchSchema.StringPropertyConfig.TokenizerType int tokenizerType) { 350 StringIndexingConfig.TokenizerType.Code tokenizerTypeProto = 351 StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType); 352 if (tokenizerTypeProto == null) { 353 throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType); 354 } 355 return tokenizerTypeProto; 356 } 357 358 @NonNull convertNumericMatchTypeToProto( @ppSearchSchema.LongPropertyConfig.IndexingType int indexingType)359 private static IntegerIndexingConfig.NumericMatchType.Code convertNumericMatchTypeToProto( 360 @AppSearchSchema.LongPropertyConfig.IndexingType int indexingType) { 361 switch (indexingType) { 362 case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE: 363 return IntegerIndexingConfig.NumericMatchType.Code.UNKNOWN; 364 case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE: 365 return IntegerIndexingConfig.NumericMatchType.Code.RANGE; 366 default: 367 throw new IllegalArgumentException("Invalid indexingType: " + indexingType); 368 } 369 } 370 371 @AppSearchSchema.LongPropertyConfig.IndexingType convertNumericMatchTypeFromProto( @onNull IntegerIndexingConfig.NumericMatchType.Code numericMatchType)372 private static int convertNumericMatchTypeFromProto( 373 @NonNull IntegerIndexingConfig.NumericMatchType.Code numericMatchType) { 374 switch (numericMatchType) { 375 case UNKNOWN: 376 return AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE; 377 case RANGE: 378 return AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE; 379 } 380 // Avoid crashing in the 'read' path; we should try to interpret the document to the 381 // extent possible. 382 Log.w(TAG, "Invalid indexingType: " + numericMatchType.getNumber()); 383 return AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE; 384 } 385 386 @NonNull 387 private static EmbeddingIndexingConfig.EmbeddingIndexingType.Code convertEmbeddingIndexingTypeToProto( @ppSearchSchema.EmbeddingPropertyConfig.IndexingType int indexingType)388 convertEmbeddingIndexingTypeToProto( 389 @AppSearchSchema.EmbeddingPropertyConfig.IndexingType int indexingType) { 390 switch (indexingType) { 391 case AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE: 392 return EmbeddingIndexingConfig.EmbeddingIndexingType.Code.UNKNOWN; 393 case AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_SIMILARITY: 394 return EmbeddingIndexingConfig.EmbeddingIndexingType.Code.LINEAR_SEARCH; 395 default: 396 throw new IllegalArgumentException("Invalid indexingType: " + indexingType); 397 } 398 } 399 400 @AppSearchSchema.EmbeddingPropertyConfig.IndexingType convertEmbeddingIndexingTypeFromProto( @onNull EmbeddingIndexingConfig.EmbeddingIndexingType.Code indexingType)401 private static int convertEmbeddingIndexingTypeFromProto( 402 @NonNull EmbeddingIndexingConfig.EmbeddingIndexingType.Code indexingType) { 403 switch (indexingType) { 404 case UNKNOWN: 405 return AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE; 406 case LINEAR_SEARCH: 407 return AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_SIMILARITY; 408 } 409 // Avoid crashing in the 'read' path; we should try to interpret the document to the 410 // extent possible. 411 Log.w(TAG, "Invalid indexingType: " + indexingType.getNumber()); 412 return AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE; 413 } 414 } 415