1 /* <lambda>null2 * Copyright (C) 2017 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 androidx.room.vo 18 19 import androidx.room.BuiltInTypeConverters 20 import androidx.room.compiler.codegen.CodeLanguage 21 import androidx.room.compiler.codegen.XCodeBlock 22 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.applyTo 23 import androidx.room.compiler.codegen.XTypeName 24 import androidx.room.compiler.codegen.buildCodeBlock 25 import androidx.room.compiler.processing.XNullability 26 import androidx.room.ext.CollectionTypeNames.ARRAY_MAP 27 import androidx.room.ext.CollectionTypeNames.LONG_SPARSE_ARRAY 28 import androidx.room.ext.CommonTypeNames 29 import androidx.room.ext.CommonTypeNames.ARRAY_LIST 30 import androidx.room.ext.CommonTypeNames.HASH_MAP 31 import androidx.room.ext.CommonTypeNames.HASH_SET 32 import androidx.room.ext.KotlinCollectionMemberNames 33 import androidx.room.ext.KotlinCollectionMemberNames.MUTABLE_LIST_OF 34 import androidx.room.ext.KotlinCollectionMemberNames.MUTABLE_SET_OF 35 import androidx.room.ext.RoomTypeNames.BYTE_ARRAY_WRAPPER 36 import androidx.room.ext.capitalize 37 import androidx.room.ext.stripNonJava 38 import androidx.room.parser.ParsedQuery 39 import androidx.room.parser.SQLTypeAffinity 40 import androidx.room.parser.SqlParser 41 import androidx.room.processor.Context 42 import androidx.room.processor.ProcessorErrors 43 import androidx.room.processor.ProcessorErrors.ISSUE_TRACKER_LINK 44 import androidx.room.processor.ProcessorErrors.relationAffinityMismatch 45 import androidx.room.processor.ProcessorErrors.relationJunctionChildAffinityMismatch 46 import androidx.room.processor.ProcessorErrors.relationJunctionParentAffinityMismatch 47 import androidx.room.solver.CodeGenScope 48 import androidx.room.solver.query.parameter.QueryParameterAdapter 49 import androidx.room.solver.query.result.RowAdapter 50 import androidx.room.solver.query.result.SingleColumnRowAdapter 51 import androidx.room.solver.types.StatementValueReader 52 import androidx.room.verifier.DatabaseVerificationErrors 53 import androidx.room.writer.QueryWriter 54 import androidx.room.writer.RelationCollectorFunctionWriter 55 import androidx.room.writer.RelationCollectorFunctionWriter.Companion.PARAM_CONNECTION_VARIABLE 56 import java.util.Locale 57 58 /** Internal class that is used to manage fetching 1/N to N relationships. */ 59 data class RelationCollector( 60 val relation: Relation, 61 // affinity between relation fields 62 val affinity: SQLTypeAffinity, 63 // concrete map type name to store relationship 64 val mapTypeName: XTypeName, 65 // map key type name, not the same as the parent or entity field type 66 val keyTypeName: XTypeName, 67 // map value type name, it is assignable to the @Relation field 68 val relationTypeName: XTypeName, 69 // query writer for the relating entity query 70 val queryWriter: QueryWriter, 71 // key reader for the parent field 72 val parentKeyColumnReader: StatementValueReader, 73 // key reader for the entity field 74 val entityKeyColumnReader: StatementValueReader, 75 // adapter for the relating pojo 76 val rowAdapter: RowAdapter, 77 // parsed relating entity query 78 val loadAllQuery: ParsedQuery, 79 // true if `relationTypeName` is a Collection, when it is `relationTypeName` is always non null. 80 val relationTypeIsCollection: Boolean 81 ) { 82 // variable name of map containing keys to relation collections, set when writing the code 83 // generator in writeInitCode 84 private lateinit var varName: String 85 86 fun writeInitCode(scope: CodeGenScope) { 87 varName = 88 scope.getTmpVar( 89 "_collection${relation.property.getPath().stripNonJava().capitalize(Locale.US)}" 90 ) 91 scope.builder.applyTo { language -> 92 if ( 93 language == CodeLanguage.JAVA || 94 mapTypeName.rawTypeName == ARRAY_MAP || 95 mapTypeName.rawTypeName == LONG_SPARSE_ARRAY 96 ) { 97 addLocalVariable( 98 name = varName, 99 typeName = mapTypeName, 100 assignExpr = XCodeBlock.ofNewInstance(mapTypeName) 101 ) 102 } else { 103 addLocalVal( 104 name = varName, 105 typeName = mapTypeName, 106 "%M()", 107 KotlinCollectionMemberNames.MUTABLE_MAP_OF 108 ) 109 } 110 } 111 } 112 113 // called to extract the key if it exists and adds it to the map of relations to fetch. 114 fun writeReadParentKeyCode( 115 stmtVarName: String, 116 fieldsWithIndices: List<PropertyWithIndex>, 117 scope: CodeGenScope 118 ) { 119 val indexVar = 120 fieldsWithIndices.firstOrNull { it.property === relation.parentProperty }?.indexVar 121 checkNotNull(indexVar) { 122 "Expected an index var for a column named '${relation.parentProperty.columnName}' to " + 123 "query the '${relation.dataClassType}' @Relation but didn't. Please file a bug at " + 124 ISSUE_TRACKER_LINK 125 } 126 scope.builder.apply { 127 readKey(stmtVarName, indexVar, parentKeyColumnReader, scope) { tmpVar -> 128 // for relation collection put an empty collections in the map, otherwise put nulls 129 if (relationTypeIsCollection) { 130 beginControlFlow("if (!%L.containsKey(%L))", varName, tmpVar).apply { 131 val newEmptyCollection = buildCodeBlock { language -> 132 when (language) { 133 CodeLanguage.JAVA -> add("new %T()", relationTypeName) 134 CodeLanguage.KOTLIN -> 135 if ( 136 relationTypeName.rawTypeName == CommonTypeNames.MUTABLE_SET 137 ) { 138 add("%M()", MUTABLE_SET_OF) 139 } else { 140 add("%M()", MUTABLE_LIST_OF) 141 } 142 } 143 } 144 addStatement("%L.put(%L, %L)", varName, tmpVar, newEmptyCollection) 145 } 146 endControlFlow() 147 } else { 148 addStatement("%L.put(%L, null)", varName, tmpVar) 149 } 150 } 151 } 152 } 153 154 // called to extract key and relation collection, defaulting to empty collection if not found 155 fun writeReadCollectionIntoTmpVar( 156 stmtVarName: String, 157 propertiesWithIndices: List<PropertyWithIndex>, 158 scope: CodeGenScope 159 ): Pair<String, Property> { 160 val indexVar = 161 propertiesWithIndices.firstOrNull { it.property === relation.parentProperty }?.indexVar 162 checkNotNull(indexVar) { 163 "Expected an index var for a column named '${relation.parentProperty.columnName}' to " + 164 "query the '${relation.dataClassType}' @Relation but didn't. Please file a bug at " + 165 ISSUE_TRACKER_LINK 166 } 167 val tmpVarNameSuffix = if (relationTypeIsCollection) "Collection" else "" 168 val tmpRelationVar = 169 scope.getTmpVar( 170 "_tmp${relation.property.name.stripNonJava().capitalize(Locale.US)}$tmpVarNameSuffix" 171 ) 172 scope.builder.apply { 173 addLocalVariable(name = tmpRelationVar, typeName = relationTypeName) 174 readKey( 175 stmtVarName = stmtVarName, 176 indexVar = indexVar, 177 keyReader = parentKeyColumnReader, 178 scope = scope, 179 onKeyReady = { tmpKeyVar -> 180 if (relationTypeIsCollection) { 181 // For Kotlin use getValue() as get() return a nullable value, when the 182 // relation is a collection the map is pre-filled with empty collection 183 // values for all keys, so this is safe. Special case for LongSParseArray 184 // since it does not have a getValue() from Kotlin. 185 val usingLongSparseArray = mapTypeName.rawTypeName == LONG_SPARSE_ARRAY 186 applyTo { language -> 187 when (language) { 188 CodeLanguage.JAVA -> 189 addStatement( 190 "%L = %L.get(%L)", 191 tmpRelationVar, 192 varName, 193 tmpKeyVar 194 ) 195 CodeLanguage.KOTLIN -> 196 if (usingLongSparseArray) { 197 addStatement( 198 "%L = checkNotNull(%L.get(%L))", 199 tmpRelationVar, 200 varName, 201 tmpKeyVar 202 ) 203 } else { 204 addStatement( 205 "%L = %L.getValue(%L)", 206 tmpRelationVar, 207 varName, 208 tmpKeyVar 209 ) 210 } 211 } 212 } 213 } else { 214 addStatement("%L = %L.get(%L)", tmpRelationVar, varName, tmpKeyVar) 215 if (relation.property.nonNull) { 216 applyTo(CodeLanguage.KOTLIN) { 217 beginControlFlow("if (%L == null)", tmpRelationVar) 218 addStatement( 219 "error(%S)", 220 "Relationship item '${relation.property.name}' was expected to" + 221 " be NON-NULL but is NULL in @Relation involving " + 222 "a parent column named '${relation.parentProperty.columnName}' and " + 223 "entityColumn named '${relation.entityProperty.columnName}'." 224 ) 225 endControlFlow() 226 } 227 } 228 } 229 }, 230 onKeyUnavailable = { 231 if (relationTypeIsCollection) { 232 val newEmptyCollection = buildCodeBlock { language -> 233 when (language) { 234 CodeLanguage.JAVA -> add("new %T()", relationTypeName) 235 CodeLanguage.KOTLIN -> 236 if ( 237 relationTypeName.rawTypeName == CommonTypeNames.MUTABLE_SET 238 ) { 239 add("%M()", MUTABLE_SET_OF) 240 } else { 241 add("%M()", MUTABLE_LIST_OF) 242 } 243 } 244 } 245 addStatement("%L = %L", tmpRelationVar, newEmptyCollection) 246 } else { 247 addStatement("%L = null", tmpRelationVar) 248 } 249 } 250 ) 251 } 252 return tmpRelationVar to relation.property 253 } 254 255 // called to write the invocation to the fetch relationship function 256 fun writeFetchRelationCall(scope: CodeGenScope) { 257 val function = scope.writer.getOrCreateFunction(RelationCollectorFunctionWriter(this)) 258 scope.builder.apply { 259 addStatement("%L(%L, %L)", function.name, PARAM_CONNECTION_VARIABLE, varName) 260 } 261 } 262 263 // called to read key and call `onKeyReady` to write code once it is successfully read 264 fun readKey( 265 stmtVarName: String, 266 indexVar: String, 267 keyReader: StatementValueReader, 268 scope: CodeGenScope, 269 onKeyReady: XCodeBlock.Builder.(String) -> Unit 270 ) { 271 readKey(stmtVarName, indexVar, keyReader, scope, onKeyReady, null) 272 } 273 274 // called to read key and call `onKeyReady` to write code once it is successfully read and 275 // `onKeyUnavailable` if the key is unavailable (missing column due to bad projection). 276 private fun readKey( 277 stmtVarName: String, 278 indexVar: String, 279 keyReader: StatementValueReader, 280 scope: CodeGenScope, 281 onKeyReady: XCodeBlock.Builder.(String) -> Unit, 282 onKeyUnavailable: (XCodeBlock.Builder.() -> Unit)?, 283 ) { 284 scope.builder.apply { 285 val tmpVar = scope.getTmpVar("_tmpKey") 286 addLocalVariable(tmpVar, keyReader.typeMirror().asTypeName()) 287 keyReader.readFromStatement(tmpVar, stmtVarName, indexVar, scope) 288 if (keyReader.typeMirror().nullability == XNullability.NONNULL) { 289 onKeyReady(tmpVar) 290 } else { 291 beginControlFlow("if (%L != null)", tmpVar) 292 onKeyReady(tmpVar) 293 if (onKeyUnavailable != null) { 294 nextControlFlow("else") 295 onKeyUnavailable() 296 } 297 endControlFlow() 298 } 299 } 300 } 301 302 /** 303 * Adapter for binding a LongSparseArray keys into query arguments. This special adapter is only 304 * used for binding the relationship query whose keys have INTEGER affinity. 305 */ 306 private class LongSparseArrayKeyQueryParameterAdapter : QueryParameterAdapter(true) { 307 override fun bindToStmt( 308 inputVarName: String, 309 stmtVarName: String, 310 startIndexVarName: String, 311 scope: CodeGenScope 312 ) { 313 val itrIndexVar = "i" 314 val itrItemVar = scope.getTmpVar("_item") 315 scope.builder.applyTo { language -> 316 when (language) { 317 CodeLanguage.JAVA -> 318 beginControlFlow( 319 "for (int %L = 0; %L < %L.size(); i++)", 320 itrIndexVar, 321 itrIndexVar, 322 inputVarName 323 ) 324 CodeLanguage.KOTLIN -> 325 beginControlFlow("for (%L in 0 until %L.size())", itrIndexVar, inputVarName) 326 }.apply { 327 addLocalVal( 328 itrItemVar, 329 XTypeName.PRIMITIVE_LONG, 330 "%L.keyAt(%L)", 331 inputVarName, 332 itrIndexVar 333 ) 334 addStatement("%L.bindLong(%L, %L)", stmtVarName, startIndexVarName, itrItemVar) 335 addStatement("%L++", startIndexVarName) 336 } 337 endControlFlow() 338 } 339 } 340 341 override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) { 342 scope.builder.addLocalVal( 343 outputVarName, 344 XTypeName.PRIMITIVE_INT, 345 "%L.size()", 346 inputVarName 347 ) 348 } 349 } 350 351 companion object { 352 353 private val LONG_SPARSE_ARRAY_KEY_QUERY_PARAM_ADAPTER = 354 LongSparseArrayKeyQueryParameterAdapter() 355 356 fun createCollectors( 357 baseContext: Context, 358 relations: List<Relation> 359 ): List<RelationCollector> { 360 return relations 361 .map { relation -> 362 val context = 363 baseContext.fork( 364 element = relation.property.element, 365 forceSuppressedWarnings = setOf(Warning.QUERY_MISMATCH), 366 forceBuiltInConverters = 367 BuiltInConverterFlags.DEFAULT.copy( 368 byteBuffer = BuiltInTypeConverters.State.ENABLED 369 ) 370 ) 371 val affinity = affinityFor(context, relation) 372 val keyTypeName = keyTypeFor(context, affinity) 373 val (relationTypeName, isRelationCollection) = 374 relationTypeFor(context, relation) 375 val tmpMapTypeName = 376 temporaryMapTypeFor(context, affinity, keyTypeName, relationTypeName) 377 378 val loadAllQuery = relation.createLoadAllSql() 379 val parsedQuery = SqlParser.parse(loadAllQuery) 380 context.checker.check( 381 parsedQuery.errors.isEmpty(), 382 relation.property.element, 383 parsedQuery.errors.joinToString("\n") 384 ) 385 if (parsedQuery.errors.isEmpty()) { 386 val resultInfo = context.databaseVerifier?.analyze(loadAllQuery) 387 parsedQuery.resultInfo = resultInfo 388 if (resultInfo?.error != null) { 389 context.logger.e( 390 relation.property.element, 391 DatabaseVerificationErrors.cannotVerifyQuery(resultInfo.error) 392 ) 393 } 394 } 395 val resultInfo = parsedQuery.resultInfo 396 397 val usingLongSparseArray = tmpMapTypeName.rawTypeName == LONG_SPARSE_ARRAY 398 val queryParam = 399 if (usingLongSparseArray) { 400 val longSparseArrayElement = 401 context.processingEnv.requireTypeElement( 402 LONG_SPARSE_ARRAY.canonicalName 403 ) 404 QueryParameter( 405 name = RelationCollectorFunctionWriter.PARAM_MAP_VARIABLE, 406 sqlName = RelationCollectorFunctionWriter.PARAM_MAP_VARIABLE, 407 type = longSparseArrayElement.type, 408 queryParamAdapter = LONG_SPARSE_ARRAY_KEY_QUERY_PARAM_ADAPTER 409 ) 410 } else { 411 val keyTypeMirror = context.processingEnv.requireType(keyTypeName) 412 val set = context.processingEnv.requireTypeElement(CommonTypeNames.SET) 413 val keySet = context.processingEnv.getDeclaredType(set, keyTypeMirror) 414 QueryParameter( 415 name = RelationCollectorFunctionWriter.KEY_SET_VARIABLE, 416 sqlName = RelationCollectorFunctionWriter.KEY_SET_VARIABLE, 417 type = keySet, 418 queryParamAdapter = 419 context.typeAdapterStore.findQueryParameterAdapter( 420 typeMirror = keySet, 421 isMultipleParameter = true 422 ) 423 ) 424 } 425 426 val queryWriter = 427 QueryWriter( 428 parameters = listOf(queryParam), 429 sectionToParamMapping = 430 listOf(Pair(parsedQuery.bindSections.first(), queryParam)), 431 query = parsedQuery 432 ) 433 434 val parentKeyColumnReader = 435 context.typeAdapterStore.findStatementValueReader( 436 output = 437 context.processingEnv.requireType(keyTypeName).let { 438 if (!relation.parentProperty.nonNull) it.makeNullable() else it 439 }, 440 affinity = affinity 441 ) 442 val entityKeyColumnReader = 443 context.typeAdapterStore.findStatementValueReader( 444 output = 445 context.processingEnv.requireType(keyTypeName).let { keyType -> 446 if (!relation.entityProperty.nonNull) keyType.makeNullable() 447 else keyType 448 }, 449 affinity = affinity 450 ) 451 // We should always find a readers since key types all have built in converters 452 check(parentKeyColumnReader != null && entityKeyColumnReader != null) { 453 "Missing one of the relation key value reader for type $keyTypeName" 454 } 455 456 // row adapter that matches full response 457 fun getDefaultRowAdapter(): RowAdapter? { 458 return context.typeAdapterStore.findRowAdapter( 459 relation.dataClassType, 460 parsedQuery 461 ) 462 } 463 val rowAdapter = 464 if ( 465 relation.projection.size == 1 && 466 resultInfo != null && 467 (resultInfo.columns.size == 1 || resultInfo.columns.size == 2) 468 ) { 469 // check for a column adapter first 470 val cursorReader = 471 context.typeAdapterStore.findStatementValueReader( 472 relation.dataClassType, 473 resultInfo.columns.first().type 474 ) 475 if (cursorReader == null) { 476 getDefaultRowAdapter() 477 } else { 478 SingleColumnRowAdapter(cursorReader) 479 } 480 } else { 481 getDefaultRowAdapter() 482 } 483 484 if (rowAdapter == null) { 485 context.logger.e( 486 relation.property.element, 487 ProcessorErrors.cannotFindQueryResultAdapter( 488 relation.dataClassType.asTypeName().toString(context.codeLanguage) 489 ) 490 ) 491 null 492 } else { 493 RelationCollector( 494 relation = relation, 495 affinity = affinity, 496 mapTypeName = tmpMapTypeName, 497 keyTypeName = keyTypeName, 498 relationTypeName = relationTypeName, 499 queryWriter = queryWriter, 500 parentKeyColumnReader = parentKeyColumnReader, 501 entityKeyColumnReader = entityKeyColumnReader, 502 rowAdapter = rowAdapter, 503 loadAllQuery = parsedQuery, 504 relationTypeIsCollection = isRelationCollection 505 ) 506 } 507 } 508 .filterNotNull() 509 } 510 511 // Gets and check the affinity of the relating columns. 512 private fun affinityFor(context: Context, relation: Relation): SQLTypeAffinity { 513 fun checkAffinity( 514 first: SQLTypeAffinity?, 515 second: SQLTypeAffinity?, 516 onAffinityMismatch: () -> Unit 517 ) = 518 if (first != null && first == second) { 519 first 520 } else { 521 onAffinityMismatch() 522 SQLTypeAffinity.TEXT 523 } 524 525 val parentAffinity = relation.parentProperty.statementValueReader?.affinity() 526 val childAffinity = relation.entityProperty.statementValueReader?.affinity() 527 val junctionParentAffinity = 528 relation.junction?.parentProperty?.statementValueReader?.affinity() 529 val junctionChildAffinity = 530 relation.junction?.entityProperty?.statementValueReader?.affinity() 531 return if (relation.junction != null) { 532 checkAffinity(childAffinity, junctionChildAffinity) { 533 context.logger.w( 534 Warning.RELATION_TYPE_MISMATCH, 535 relation.property.element, 536 relationJunctionChildAffinityMismatch( 537 childColumn = relation.entityProperty.columnName, 538 junctionChildColumn = relation.junction.entityProperty.columnName, 539 childAffinity = childAffinity, 540 junctionChildAffinity = junctionChildAffinity 541 ) 542 ) 543 } 544 checkAffinity(parentAffinity, junctionParentAffinity) { 545 context.logger.w( 546 Warning.RELATION_TYPE_MISMATCH, 547 relation.property.element, 548 relationJunctionParentAffinityMismatch( 549 parentColumn = relation.parentProperty.columnName, 550 junctionParentColumn = relation.junction.parentProperty.columnName, 551 parentAffinity = parentAffinity, 552 junctionParentAffinity = junctionParentAffinity 553 ) 554 ) 555 } 556 } else { 557 checkAffinity(parentAffinity, childAffinity) { 558 context.logger.w( 559 Warning.RELATION_TYPE_MISMATCH, 560 relation.property.element, 561 relationAffinityMismatch( 562 parentColumn = relation.parentProperty.columnName, 563 childColumn = relation.entityProperty.columnName, 564 parentAffinity = parentAffinity, 565 childAffinity = childAffinity 566 ) 567 ) 568 } 569 } 570 } 571 572 // Gets the resulting relation type name. (i.e. the Pojo's @Relation field type name.) 573 private fun relationTypeFor(context: Context, relation: Relation) = 574 relation.property.type.let { fieldType -> 575 if (fieldType.typeArguments.isNotEmpty()) { 576 val rawType = fieldType.rawType 577 val setType = context.processingEnv.requireType(CommonTypeNames.MUTABLE_SET) 578 val paramTypeName = 579 if (rawType.isAssignableFrom(setType.rawType)) { 580 when (context.codeLanguage) { 581 CodeLanguage.KOTLIN -> 582 CommonTypeNames.MUTABLE_SET.parametrizedBy( 583 relation.dataClassTypeName 584 ) 585 CodeLanguage.JAVA -> 586 HASH_SET.parametrizedBy(relation.dataClassTypeName) 587 } 588 } else { 589 when (context.codeLanguage) { 590 CodeLanguage.KOTLIN -> 591 CommonTypeNames.MUTABLE_LIST.parametrizedBy( 592 relation.dataClassTypeName 593 ) 594 CodeLanguage.JAVA -> 595 ARRAY_LIST.parametrizedBy(relation.dataClassTypeName) 596 } 597 } 598 paramTypeName to true 599 } else { 600 relation.dataClassTypeName.copy(nullable = true) to false 601 } 602 } 603 604 // Gets the type name of the temporary key map. 605 private fun temporaryMapTypeFor( 606 context: Context, 607 affinity: SQLTypeAffinity, 608 keyTypeName: XTypeName, 609 valueTypeName: XTypeName, 610 ): XTypeName { 611 val canUseLongSparseArray = 612 context.processingEnv.findTypeElement(LONG_SPARSE_ARRAY.canonicalName) != null 613 val canUseArrayMap = 614 context.processingEnv.findTypeElement(ARRAY_MAP.canonicalName) != null && 615 context.isAndroidOnlyTarget() 616 return when { 617 canUseLongSparseArray && affinity == SQLTypeAffinity.INTEGER -> 618 LONG_SPARSE_ARRAY.parametrizedBy(valueTypeName) 619 canUseArrayMap -> ARRAY_MAP.parametrizedBy(keyTypeName, valueTypeName) 620 else -> 621 when (context.codeLanguage) { 622 CodeLanguage.JAVA -> HASH_MAP.parametrizedBy(keyTypeName, valueTypeName) 623 CodeLanguage.KOTLIN -> 624 CommonTypeNames.MUTABLE_MAP.parametrizedBy(keyTypeName, valueTypeName) 625 } 626 } 627 } 628 629 // Gets the type name of the relationship key. 630 private fun keyTypeFor(context: Context, affinity: SQLTypeAffinity): XTypeName { 631 val canUseLongSparseArray = 632 context.processingEnv.findTypeElement(LONG_SPARSE_ARRAY.canonicalName) != null 633 return when (affinity) { 634 SQLTypeAffinity.INTEGER -> 635 if (canUseLongSparseArray) { 636 XTypeName.PRIMITIVE_LONG 637 } else { 638 XTypeName.BOXED_LONG 639 } 640 SQLTypeAffinity.REAL -> XTypeName.BOXED_DOUBLE 641 SQLTypeAffinity.TEXT -> CommonTypeNames.STRING 642 SQLTypeAffinity.BLOB -> BYTE_ARRAY_WRAPPER 643 else -> { 644 // no affinity default to String 645 CommonTypeNames.STRING 646 } 647 } 648 } 649 } 650 } 651