Skip to content

Commit

Permalink
Add deferrability options to foreign constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
jnfeinstein committed Jan 13, 2021
1 parent c390ab0 commit 368a51b
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ enum class ReferenceOption {
}
}

/**
* Represents constraint check timing options
* @see <a href="https://www.postgresql.org/docs/9.5/sql-set-constraints.html">PostgreSQL Documentation</a>
*/
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.
*/
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -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<String> = listOf("ALTER TABLE $fromTable ADD $foreignKeyPart")
Expand Down
22 changes: 16 additions & 6 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt
Original file line number Diff line number Diff line change
Expand Up @@ -720,13 +720,15 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
ref: Column<T>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): C = apply {
this.foreignKey = ForeignKeyConstraint(
target = ref,
from = this,
onUpdate = onUpdate,
onDelete = onDelete,
deferrable = deferrable,
name = fkName
)
}
Expand All @@ -747,13 +749,15 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
ref: Column<EntityID<T>>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): C = apply {
this.foreignKey = ForeignKeyConstraint(
target = ref,
from = this,
onUpdate = onUpdate,
onDelete = onDelete,
deferrable = deferrable,
name = fkName
)
}
Expand All @@ -776,9 +780,10 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
refColumn: Column<T>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): Column<T> {
val column = Column<T>(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, fkName)
val column = Column<T>(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, deferrable, fkName)
_columns.addColumn(column)
return column
}
Expand All @@ -803,10 +808,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
refColumn: Column<E>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): Column<E> {
val entityIDColumn = entityId(name, (refColumn.columnType as EntityIDColumnType<T>).idColumn) as Column<E>
return entityIDColumn.references(refColumn, onDelete, onUpdate, fkName)
return entityIDColumn.references(refColumn, onDelete, onUpdate, deferrable, fkName)
}

/**
Expand All @@ -827,8 +833,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
foreign: IdTable<T>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): Column<EntityID<T>> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, fkName)
): Column<EntityID<T>> = 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.
Expand All @@ -848,8 +855,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
refColumn: Column<T>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): Column<T?> = Column<T>(this, name, refColumn.columnType.cloneAsBaseType()).references(refColumn, onDelete, onUpdate, fkName).nullable()
): Column<T?> = Column<T>(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.
Expand All @@ -871,10 +879,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
refColumn: Column<E>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): Column<E?> {
val entityIdColumn = entityId(name, (refColumn.columnType as EntityIDColumnType<T>).idColumn) as Column<E>
return entityIdColumn.references(refColumn, onDelete, onUpdate, fkName).nullable()
return entityIdColumn.references(refColumn, onDelete, onUpdate, deferrable, fkName).nullable()
}

/**
Expand All @@ -895,8 +904,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
foreign: IdTable<T>,
onDelete: ReferenceOption? = null,
onUpdate: ReferenceOption? = null,
deferrable: DeferrabilityOption? = null,
fkName: String? = null
): Column<EntityID<T>?> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, fkName).nullable()
): Column<EntityID<T>?> = entityId(name, foreign).references(foreign.id, onDelete, onUpdate, deferrable, fkName).nullable()

// Miscellaneous

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq
from = fromColumn,
onUpdate = constraintUpdateRule,
onDelete = constraintDeleteRule,
deferrable = null,
name = constraintName
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 368a51b

Please sign in to comment.