diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt index 9353773b69..5bf5fff237 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt @@ -43,6 +43,28 @@ enum class ReferenceOption { } } +/** + * Represents constraint check timing options + * @see PostgreSQL Documentation + */ +enum class DeferrabilityOption { + NOT_DEFERRABLE, + DEFERRABLE_INITIALLY_DEFERRED, + DEFERRABLE_INITIALLY_IMMEDIATE; + + override fun toString(): String = name.replace("_", " ") + + companion object { + /** Returns the corresponding [DeferrabilityOption] for the specified [refOption] from JDBC. */ + fun resolveRefOptionFromJdbc(refOption: Int): DeferrabilityOption? = when (refOption) { + DatabaseMetaData.importedKeyNotDeferrable -> NOT_DEFERRABLE + DatabaseMetaData.importedKeyInitiallyDeferred -> DEFERRABLE_INITIALLY_DEFERRED + DatabaseMetaData.importedKeyInitiallyImmediate -> DEFERRABLE_INITIALLY_IMMEDIATE + else -> currentDialect.defaultDeferrabilityOption + } + } +} + /** * Represents a foreign key constraint. */ @@ -51,6 +73,7 @@ data class ForeignKeyConstraint( val from: Column<*>, private val onUpdate: ReferenceOption?, private val onDelete: ReferenceOption?, + private val deferrable: DeferrabilityOption?, private val name: String? ) : DdlAware { private val tx: Transaction @@ -73,6 +96,9 @@ data class ForeignKeyConstraint( /** Reference option when performing delete operations. */ val deleteRule: ReferenceOption? get() = onDelete ?: currentDialectIfAvailable?.defaultReferenceOption + /** Constraint check timing when supported. */ + val deferrabilityRule: DeferrabilityOption? + get() = deferrable ?: currentDialectIfAvailable?.defaultDeferrabilityOption /** Name of this constraint. */ val fkName: String get() = tx.db.identifierManager.cutIfNecessaryAndQuote( @@ -93,6 +119,14 @@ data class ForeignKeyConstraint( append(" ON UPDATE $updateRule") } } + + deferrabilityRule?.let { + if (currentDialect is MysqlDialect) { + exposedLogger.warn("MySQL does not support deferred constraints.") + } else { + append(" $it") + } + } } override fun createStatement(): List = listOf("ALTER TABLE $fromTable ADD $foreignKeyPart") diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt index 78842e4128..6f229c4ead 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt @@ -720,6 +720,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { ref: Column, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null ): C = apply { this.foreignKey = ForeignKeyConstraint( @@ -727,6 +728,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { from = this, onUpdate = onUpdate, onDelete = onDelete, + deferrable = deferrable, name = fkName ) } @@ -747,6 +749,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { ref: Column>, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null ): C = apply { this.foreignKey = ForeignKeyConstraint( @@ -754,6 +757,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { from = this, onUpdate = onUpdate, onDelete = onDelete, + deferrable = deferrable, name = fkName ) } @@ -776,9 +780,10 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { refColumn: Column, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null ): Column { - val column = Column(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, fkName) + val column = Column(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, deferrable, fkName) _columns.addColumn(column) return column } @@ -803,10 +808,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { refColumn: Column, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null ): Column { val entityIDColumn = entityId(name, (refColumn.columnType as EntityIDColumnType).idColumn) as Column - return entityIDColumn.references(refColumn, onDelete, onUpdate, fkName) + return entityIDColumn.references(refColumn, onDelete, onUpdate, deferrable, fkName) } /** @@ -827,8 +833,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { foreign: IdTable, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null - ): Column> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, fkName) + ): Column> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, deferrable, fkName) /** * Creates a column with the specified [name] with an optional reference to the [refColumn] column with [onDelete], [onUpdate], and [fkName] options. @@ -848,8 +855,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { refColumn: Column, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null - ): Column = Column(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, fkName).nullable() + ): Column = Column(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, deferrable, fkName).nullable() /** * Creates a column with the specified [name] with an optional reference to the [refColumn] column with [onDelete], [onUpdate], and [fkName] options. @@ -871,10 +879,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { refColumn: Column, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null ): Column { val entityIdColumn = entityId(name, (refColumn.columnType as EntityIDColumnType).idColumn) as Column - return entityIdColumn.references(refColumn, onDelete, onUpdate, fkName).nullable() + return entityIdColumn.references(refColumn, onDelete, onUpdate, deferrable, fkName).nullable() } /** @@ -895,8 +904,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { foreign: IdTable, onDelete: ReferenceOption? = null, onUpdate: ReferenceOption? = null, + deferrable: DeferrabilityOption? = null, fkName: String? = null - ): Column?> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, fkName).nullable() + ): Column?> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, deferrable, fkName).nullable() // Miscellaneous diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt index c0ea85a7f0..b0b19b1ec4 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt @@ -512,6 +512,8 @@ interface DatabaseDialect { val needsSequenceToAutoInc: Boolean get() = false /** Returns the default reference option for the dialect. */ val defaultReferenceOption: ReferenceOption get() = ReferenceOption.RESTRICT + /** Returns the default constraint timing for the dialect. */ + val defaultDeferrabilityOption: DeferrabilityOption? get() = null /** Returns `true` if the dialect requires the use of quotes when using symbols in object names, `false` otherwise. */ val needsQuotesWhenSymbolsInNames: Boolean get() = true /** Returns `true` if the dialect supports returning multiple generated keys as a result of an insert operation, `false` otherwise. */ diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt index 10e32e9bb8..6c5478e8f2 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt @@ -172,6 +172,7 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq from = fromColumn, onUpdate = constraintUpdateRule, onDelete = constraintDeleteRule, + deferrable = null, name = constraintName ) ) diff --git a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt index 9a12904625..7de55154e5 100644 --- a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt +++ b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt @@ -191,11 +191,14 @@ class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData) } val constraintUpdateRule = ReferenceOption.resolveRefOptionFromJdbc(getInt("UPDATE_RULE")) val constraintDeleteRule = ReferenceOption.resolveRefOptionFromJdbc(getInt("DELETE_RULE")) + val constraintDeferrable = DeferrabilityOption.resolveRefOptionFromJdbc(getInt("DEFERRABILITY")) + ForeignKeyConstraint( target = targetColumn, from = fromColumn, onUpdate = constraintUpdateRule, onDelete = constraintDeleteRule, + deferrable = constraintDeferrable, name = constraintName ) }.filterNotNull()