1 /* <lambda>null2 * Copyright (C) 2016 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.processor 18 19 import androidx.room.compiler.processing.XType 20 import androidx.room.compiler.processing.XTypeElement 21 import androidx.room.ext.isNotError 22 import androidx.room.ext.isNotNone 23 import androidx.room.parser.SQLTypeAffinity 24 import androidx.room.parser.SqlParser 25 import androidx.room.processor.EntityProcessor.Companion.createIndexName 26 import androidx.room.processor.EntityProcessor.Companion.extractForeignKeys 27 import androidx.room.processor.EntityProcessor.Companion.extractIndices 28 import androidx.room.processor.EntityProcessor.Companion.extractTableName 29 import androidx.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY 30 import androidx.room.processor.ProcessorErrors.INVALID_INDEX_ORDERS_SIZE 31 import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY 32 import androidx.room.processor.cache.Cache 33 import androidx.room.vo.DataClass 34 import androidx.room.vo.EmbeddedProperty 35 import androidx.room.vo.Entity 36 import androidx.room.vo.ForeignKey 37 import androidx.room.vo.Index 38 import androidx.room.vo.PrimaryKey 39 import androidx.room.vo.Properties 40 import androidx.room.vo.Property 41 import androidx.room.vo.Warning 42 import androidx.room.vo.columnNames 43 import androidx.room.vo.findPropertyByColumnName 44 45 class TableEntityProcessor 46 internal constructor( 47 baseContext: Context, 48 val element: XTypeElement, 49 private val referenceStack: LinkedHashSet<String> = LinkedHashSet() 50 ) : EntityProcessor { 51 val context = baseContext.fork(element) 52 53 override fun process(): Entity { 54 return context.cache.entities.get(Cache.EntityKey(element)) { doProcess() } 55 } 56 57 private fun doProcess(): Entity { 58 if (!element.validate()) { 59 context.reportMissingTypeReference(element.qualifiedName) 60 return Entity( 61 element = element, 62 tableName = element.name, 63 type = element.type, 64 properties = emptyList(), 65 embeddedProperties = emptyList(), 66 indices = emptyList(), 67 primaryKey = PrimaryKey.MISSING, 68 foreignKeys = emptyList(), 69 constructor = null, 70 shadowTableName = null 71 ) 72 } 73 context.checker.hasAnnotation( 74 element, 75 androidx.room.Entity::class, 76 ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY 77 ) 78 val annotation = element.getAnnotation(androidx.room.Entity::class) 79 val tableName: String 80 val entityIndices: List<IndexInput> 81 val foreignKeyInputs: List<ForeignKeyInput> 82 val inheritSuperIndices: Boolean 83 if (annotation != null) { 84 tableName = extractTableName(element, annotation) 85 entityIndices = extractIndices(annotation, tableName) 86 inheritSuperIndices = annotation["inheritSuperIndices"]?.asBoolean() == true 87 foreignKeyInputs = extractForeignKeys(annotation) 88 } else { 89 tableName = element.name 90 foreignKeyInputs = emptyList() 91 entityIndices = emptyList() 92 inheritSuperIndices = false 93 } 94 context.checker.notBlank( 95 tableName, 96 element, 97 ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY 98 ) 99 context.checker.check( 100 !tableName.startsWith("sqlite_", true), 101 element, 102 ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_START_WITH_SQLITE 103 ) 104 105 val pojo = 106 DataClassProcessor.createFor( 107 context = context, 108 element = element, 109 bindingScope = PropertyProcessor.BindingScope.TWO_WAY, 110 parent = null, 111 referenceStack = referenceStack 112 ) 113 .process() 114 context.checker.check(pojo.relations.isEmpty(), element, RELATION_IN_ENTITY) 115 116 val propertyIndices = 117 pojo.properties 118 .filter { it.indexed } 119 .mapNotNull { 120 if (it.parent != null) { 121 it.indexed = false 122 context.logger.w( 123 Warning.INDEX_FROM_EMBEDDED_PROPERTY_IS_DROPPED, 124 it.element, 125 ProcessorErrors.droppedEmbeddedPropertyIndex( 126 it.getPath(), 127 element.qualifiedName 128 ) 129 ) 130 null 131 } else if (it.element.enclosingElement != element && !inheritSuperIndices) { 132 it.indexed = false 133 context.logger.w( 134 Warning.INDEX_FROM_PARENT_PROPERTY_IS_DROPPED, 135 ProcessorErrors.droppedSuperClassPropertyIndex( 136 it.columnName, 137 element.qualifiedName, 138 it.element.enclosingElement 139 .asClassName() 140 .toString(context.codeLanguage) 141 ) 142 ) 143 null 144 } else { 145 IndexInput( 146 name = createIndexName(listOf(it.columnName), tableName), 147 unique = false, 148 columnNames = listOf(it.columnName), 149 orders = emptyList() 150 ) 151 } 152 } 153 val superIndices = loadSuperIndices(element.superClass, tableName, inheritSuperIndices) 154 val indexInputs = entityIndices + propertyIndices + superIndices 155 val indices = validateAndCreateIndices(indexInputs, pojo) 156 157 val primaryKey = findAndValidatePrimaryKey(pojo.properties, pojo.embeddedProperties) 158 val affinity = primaryKey.properties.firstOrNull()?.affinity ?: SQLTypeAffinity.TEXT 159 context.checker.check( 160 !primaryKey.autoGenerateId || affinity == SQLTypeAffinity.INTEGER, 161 primaryKey.properties.firstOrNull()?.element ?: element, 162 ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT 163 ) 164 165 val entityForeignKeys = validateAndCreateForeignKeyReferences(foreignKeyInputs, pojo) 166 checkIndicesForForeignKeys(entityForeignKeys, primaryKey, indices) 167 168 context.checker.check( 169 SqlParser.isValidIdentifier(tableName), 170 element, 171 ProcessorErrors.INVALID_TABLE_NAME 172 ) 173 pojo.properties.forEach { 174 context.checker.check( 175 SqlParser.isValidIdentifier(it.columnName), 176 it.element, 177 ProcessorErrors.INVALID_COLUMN_NAME 178 ) 179 } 180 181 val entity = 182 Entity( 183 element = element, 184 tableName = tableName, 185 type = pojo.type, 186 properties = pojo.properties, 187 embeddedProperties = pojo.embeddedProperties, 188 indices = indices, 189 primaryKey = primaryKey, 190 foreignKeys = entityForeignKeys, 191 constructor = pojo.constructor, 192 shadowTableName = null 193 ) 194 195 return entity 196 } 197 198 private fun checkIndicesForForeignKeys( 199 entityForeignKeys: List<ForeignKey>, 200 primaryKey: PrimaryKey, 201 indices: List<Index> 202 ) { 203 fun covers(columnNames: List<String>, properties: List<Property>): Boolean = 204 properties.size >= columnNames.size && 205 columnNames.withIndex().all { properties[it.index].columnName == it.value } 206 207 entityForeignKeys.forEach { fKey -> 208 val columnNames = fKey.childProperties.map { it.columnName } 209 val exists = 210 covers(columnNames, primaryKey.properties) || 211 indices.any { index -> covers(columnNames, index.properties) } 212 if (!exists) { 213 if (columnNames.size == 1) { 214 context.logger.w( 215 Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, 216 element, 217 ProcessorErrors.foreignKeyMissingIndexInChildColumn(columnNames[0]) 218 ) 219 } else { 220 context.logger.w( 221 Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, 222 element, 223 ProcessorErrors.foreignKeyMissingIndexInChildColumns(columnNames) 224 ) 225 } 226 } 227 } 228 } 229 230 /** Does a validation on foreign keys except the parent table's columns. */ 231 private fun validateAndCreateForeignKeyReferences( 232 foreignKeyInputs: List<ForeignKeyInput>, 233 dataClass: DataClass 234 ): List<ForeignKey> { 235 return foreignKeyInputs 236 .map { 237 if (it.onUpdate == null) { 238 context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION) 239 return@map null 240 } 241 if (it.onDelete == null) { 242 context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION) 243 return@map null 244 } 245 if (it.childColumns.isEmpty()) { 246 context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST) 247 return@map null 248 } 249 if (it.parentColumns.isEmpty()) { 250 context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST) 251 return@map null 252 } 253 if (it.childColumns.size != it.parentColumns.size) { 254 context.logger.e( 255 element, 256 ProcessorErrors.foreignKeyColumnNumberMismatch( 257 it.childColumns, 258 it.parentColumns 259 ) 260 ) 261 return@map null 262 } 263 val parentElement = it.parent.typeElement 264 if (parentElement == null) { 265 context.logger.e(element, ProcessorErrors.FOREIGN_KEY_CANNOT_FIND_PARENT) 266 return@map null 267 } 268 val parentAnnotation = parentElement.getAnnotation(androidx.room.Entity::class) 269 if (parentAnnotation == null) { 270 context.logger.e( 271 element, 272 ProcessorErrors.foreignKeyNotAnEntity(parentElement.qualifiedName) 273 ) 274 return@map null 275 } 276 val tableName = extractTableName(parentElement, parentAnnotation) 277 val properties = 278 it.childColumns.mapNotNull { columnName -> 279 val property = dataClass.findPropertyByColumnName(columnName) 280 if (property == null) { 281 context.logger.e( 282 dataClass.element, 283 ProcessorErrors.foreignKeyChildColumnDoesNotExist( 284 columnName, 285 dataClass.columnNames 286 ) 287 ) 288 } 289 property 290 } 291 if (properties.size != it.childColumns.size) { 292 return@map null 293 } 294 ForeignKey( 295 parentTable = tableName, 296 childProperties = properties, 297 parentColumns = it.parentColumns, 298 onDelete = it.onDelete, 299 onUpdate = it.onUpdate, 300 deferred = it.deferred 301 ) 302 } 303 .filterNotNull() 304 } 305 306 private fun findAndValidatePrimaryKey( 307 properties: List<Property>, 308 embeddedProperties: List<EmbeddedProperty> 309 ): PrimaryKey { 310 val candidates = 311 collectPrimaryKeysFromEntityAnnotations(element, properties) + 312 collectPrimaryKeysFromPrimaryKeyAnnotations(properties) + 313 collectPrimaryKeysFromEmbeddedProperties(embeddedProperties) 314 315 context.checker.check(candidates.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY) 316 317 // 1. If a key is not autogenerated, but is Primary key or is part of Primary key we 318 // force the @NonNull annotation. If the key is a single Primary Key, Integer or Long, we 319 // don't force the @NonNull annotation since SQLite will automatically generate IDs. 320 // 2. If a key is autogenerate, we generate NOT NULL in table spec, but we don't require 321 // @NonNull annotation on the property itself. 322 val verifiedProperties = 323 mutableSetOf<Property>() // track verified properties to not over report 324 candidates 325 .filterNot { it.autoGenerateId } 326 .forEach { candidate -> 327 candidate.properties.forEach { property -> 328 if ( 329 candidate.properties.size > 1 || 330 (candidate.properties.size == 1 && 331 property.affinity != SQLTypeAffinity.INTEGER) 332 ) { 333 if (!verifiedProperties.contains(property)) { 334 context.checker.check( 335 property.nonNull, 336 property.element, 337 ProcessorErrors.primaryKeyNull(property.getPath()) 338 ) 339 verifiedProperties.add(property) 340 } 341 // Validate parents for nullability 342 var parent = property.parent 343 while (parent != null) { 344 val parentProperty = parent.property 345 if (!verifiedProperties.contains(parentProperty)) { 346 context.checker.check( 347 parentProperty.nonNull, 348 parentProperty.element, 349 ProcessorErrors.primaryKeyNull(parentProperty.getPath()) 350 ) 351 verifiedProperties.add(parentProperty) 352 } 353 parent = parentProperty.parent 354 } 355 } 356 } 357 } 358 359 if (candidates.size == 1) { 360 // easy :) 361 return candidates.first() 362 } 363 364 return choosePrimaryKey(candidates, element) 365 } 366 367 /** Check fields for @PrimaryKey. */ 368 private fun collectPrimaryKeysFromPrimaryKeyAnnotations( 369 fields: List<Property> 370 ): List<PrimaryKey> { 371 return fields.mapNotNull { field -> 372 val primaryKeyAnnotation = 373 field.element.getAnnotation(androidx.room.PrimaryKey::class) 374 ?: return@mapNotNull null 375 if (field.parent != null) { 376 // the field in the entity that contains this error. 377 val grandParentField = field.parent.mRootParent.property.element 378 // bound for entity. 379 context 380 .fork(grandParentField) 381 .logger 382 .w( 383 Warning.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED, 384 grandParentField, 385 ProcessorErrors.embeddedPrimaryKeyIsDropped( 386 element.qualifiedName, 387 field.name 388 ) 389 ) 390 null 391 } else { 392 PrimaryKey( 393 declaredIn = field.element.enclosingElement, 394 properties = Properties(field), 395 autoGenerateId = primaryKeyAnnotation["autoGenerate"]?.asBoolean() == true 396 ) 397 } 398 } 399 } 400 401 /** Check classes for @Entity(primaryKeys = ?). */ 402 private fun collectPrimaryKeysFromEntityAnnotations( 403 typeElement: XTypeElement, 404 availableProperties: List<Property> 405 ): List<PrimaryKey> { 406 val myPkeys = 407 typeElement.getAnnotation(androidx.room.Entity::class)?.let { 408 val primaryKeyColumns = it["primaryKeys"]?.asStringList() ?: emptyList() 409 if (primaryKeyColumns.isEmpty()) { 410 emptyList() 411 } else { 412 val properties = 413 primaryKeyColumns.mapNotNull { pKeyColumnName -> 414 val property = 415 availableProperties.firstOrNull { it.columnName == pKeyColumnName } 416 context.checker.check( 417 property != null, 418 typeElement, 419 ProcessorErrors.primaryKeyColumnDoesNotExist( 420 pKeyColumnName, 421 availableProperties.map { it.columnName } 422 ) 423 ) 424 property 425 } 426 listOf( 427 PrimaryKey( 428 declaredIn = typeElement, 429 properties = Properties(properties), 430 autoGenerateId = false 431 ) 432 ) 433 } 434 } ?: emptyList() 435 // checks supers. 436 val mySuper = typeElement.superClass 437 val superPKeys = 438 if (mySuper != null && mySuper.isNotNone() && mySuper.isNotError()) { 439 // my super cannot see my properties so remove them. 440 val remainingProperties = 441 availableProperties.filterNot { it.element.enclosingElement == typeElement } 442 collectPrimaryKeysFromEntityAnnotations(mySuper.typeElement!!, remainingProperties) 443 } else { 444 emptyList() 445 } 446 return superPKeys + myPkeys 447 } 448 449 private fun collectPrimaryKeysFromEmbeddedProperties( 450 embeddedProperties: List<EmbeddedProperty> 451 ): List<PrimaryKey> { 452 return embeddedProperties.mapNotNull { embeddedProperty -> 453 embeddedProperty.property.element.getAnnotation(androidx.room.PrimaryKey::class)?.let { 454 val autoGenerate = it["autoGenerate"]?.asBoolean() == true 455 context.checker.check( 456 !autoGenerate || embeddedProperty.dataClass.properties.size == 1, 457 embeddedProperty.property.element, 458 ProcessorErrors.AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_PROPERTIES 459 ) 460 PrimaryKey( 461 declaredIn = embeddedProperty.property.element.enclosingElement, 462 properties = embeddedProperty.dataClass.properties, 463 autoGenerateId = autoGenerate 464 ) 465 } 466 } 467 } 468 469 // start from my element and check if anywhere in the list we can find the only well defined 470 // pkey, if so, use it. 471 private fun choosePrimaryKey( 472 candidates: List<PrimaryKey>, 473 typeElement: XTypeElement 474 ): PrimaryKey { 475 // If 1 of these primary keys is declared in this class, then it is the winner. Just print 476 // a note for the others. 477 // If 0 is declared, check the parent. 478 // If more than 1 primary key is declared in this class, it is an error. 479 val myPKeys = candidates.filter { candidate -> candidate.declaredIn == typeElement } 480 return if (myPKeys.size == 1) { 481 // just note, this is not worth an error or warning 482 (candidates - myPKeys).forEach { 483 context.logger.d( 484 element, 485 "${it.toHumanReadableString()} is" + 486 " overridden by ${myPKeys.first().toHumanReadableString()}" 487 ) 488 } 489 myPKeys.first() 490 } else if (myPKeys.isEmpty()) { 491 // i have not declared anything, delegate to super 492 val mySuper = typeElement.superClass 493 if (mySuper != null && mySuper.isNotNone() && mySuper.isNotError()) { 494 return choosePrimaryKey(candidates, mySuper.typeElement!!) 495 } 496 PrimaryKey.MISSING 497 } else { 498 context.logger.e( 499 element, 500 ProcessorErrors.multiplePrimaryKeyAnnotations( 501 myPKeys.map(PrimaryKey::toHumanReadableString) 502 ) 503 ) 504 PrimaryKey.MISSING 505 } 506 } 507 508 private fun validateAndCreateIndices( 509 inputs: List<IndexInput>, 510 dataClass: DataClass 511 ): List<Index> { 512 // check for columns 513 val indices = 514 inputs.mapNotNull { input -> 515 context.checker.check( 516 input.columnNames.isNotEmpty(), 517 element, 518 INDEX_COLUMNS_CANNOT_BE_EMPTY 519 ) 520 val properties = 521 input.columnNames.mapNotNull { columnName -> 522 val property = dataClass.findPropertyByColumnName(columnName) 523 context.checker.check( 524 property != null, 525 element, 526 ProcessorErrors.indexColumnDoesNotExist( 527 columnName, 528 dataClass.columnNames 529 ) 530 ) 531 property 532 } 533 if (input.orders.isNotEmpty()) { 534 context.checker.check( 535 input.columnNames.size == input.orders.size, 536 element, 537 INVALID_INDEX_ORDERS_SIZE 538 ) 539 } 540 if (properties.isEmpty()) { 541 null 542 } else { 543 Index( 544 name = input.name, 545 unique = input.unique, 546 properties = Properties(properties), 547 orders = input.orders 548 ) 549 } 550 } 551 552 // check for duplicate indices 553 indices 554 .groupBy { it.name } 555 .filter { it.value.size > 1 } 556 .forEach { context.logger.e(element, ProcessorErrors.duplicateIndexInEntity(it.key)) } 557 558 // see if any embedded property is an entity with indices, if so, report a warning 559 dataClass.embeddedProperties.forEach { embedded -> 560 val embeddedElement = embedded.dataClass.element 561 embeddedElement.getAnnotation(androidx.room.Entity::class)?.let { 562 val subIndices = extractIndices(it, "") 563 if (subIndices.isNotEmpty()) { 564 context.logger.w( 565 Warning.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED, 566 embedded.property.element, 567 ProcessorErrors.droppedEmbeddedIndex( 568 entityName = embedded.dataClass.typeName.toString(context.codeLanguage), 569 propertyPath = embedded.property.getPath(), 570 grandParent = element.qualifiedName 571 ) 572 ) 573 } 574 } 575 } 576 return indices 577 } 578 579 // check if parent is an Entity, if so, report its annotation indices 580 private fun loadSuperIndices( 581 typeMirror: XType?, 582 tableName: String, 583 inherit: Boolean 584 ): List<IndexInput> { 585 if (typeMirror == null || typeMirror.isNone() || typeMirror.isError()) { 586 return emptyList() 587 } 588 val parentTypeElement = typeMirror.typeElement 589 @Suppress("FoldInitializerAndIfToElvis") 590 if (parentTypeElement == null) { 591 // this is coming from a parent, shouldn't happen so no reason to report an error 592 return emptyList() 593 } 594 val myIndices = 595 parentTypeElement.getAnnotation(androidx.room.Entity::class)?.let { annotation -> 596 val indices = extractIndices(annotation, tableName = "super") 597 if (indices.isEmpty()) { 598 emptyList() 599 } else if (inherit) { 600 // rename them 601 indices.map { 602 IndexInput( 603 name = createIndexName(it.columnNames, tableName), 604 unique = it.unique, 605 columnNames = it.columnNames, 606 orders = it.orders 607 ) 608 } 609 } else { 610 context.logger.w( 611 Warning.INDEX_FROM_PARENT_IS_DROPPED, 612 parentTypeElement, 613 ProcessorErrors.droppedSuperClassIndex( 614 childEntity = element.qualifiedName, 615 superEntity = parentTypeElement.qualifiedName 616 ) 617 ) 618 emptyList() 619 } 620 } ?: emptyList() 621 return myIndices + loadSuperIndices(parentTypeElement.superClass, tableName, inherit) 622 } 623 } 624