1/*
2 * 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
17package androidx.room.processor
18
19import androidx.room.ext.getAsBoolean
20import androidx.room.ext.getAsInt
21import androidx.room.ext.getAsString
22import androidx.room.ext.getAsStringList
23import androidx.room.ext.toType
24import androidx.room.parser.SQLTypeAffinity
25import androidx.room.parser.SqlParser
26import androidx.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
27import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
28import androidx.room.processor.cache.Cache
29import androidx.room.vo.EmbeddedField
30import androidx.room.vo.Entity
31import androidx.room.vo.Field
32import androidx.room.vo.ForeignKey
33import androidx.room.vo.ForeignKeyAction
34import androidx.room.vo.Index
35import androidx.room.vo.Pojo
36import androidx.room.vo.PrimaryKey
37import androidx.room.vo.Warning
38import com.google.auto.common.AnnotationMirrors
39import com.google.auto.common.AnnotationMirrors.getAnnotationValue
40import com.google.auto.common.MoreElements
41import com.google.auto.common.MoreTypes
42import javax.lang.model.element.AnnotationMirror
43import javax.lang.model.element.AnnotationValue
44import javax.lang.model.element.Name
45import javax.lang.model.element.TypeElement
46import javax.lang.model.type.TypeKind
47import javax.lang.model.type.TypeMirror
48import javax.lang.model.util.SimpleAnnotationValueVisitor6
49
50class 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