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()
diff --git a/exposed-tests/jetbrains.db b/exposed-tests/jetbrains.db
new file mode 100644
index 0000000000..e69de29bb2