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