Skip to content

Commit

Permalink
feat: EXPOSED-47 Add support for SET DEFAULT reference option (#1744)
Browse files Browse the repository at this point in the history
  • Loading branch information
joc-a authored Jun 9, 2023
1 parent ffabd9d commit 60d710c
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package org.jetbrains.exposed.sql

import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.*
import org.jetbrains.exposed.sql.vendors.H2Dialect
import org.jetbrains.exposed.sql.vendors.MariaDBDialect
import org.jetbrains.exposed.sql.vendors.MysqlDialect
import org.jetbrains.exposed.sql.vendors.OracleDialect
import org.jetbrains.exposed.sql.vendors.currentDialect
import org.jetbrains.exposed.sql.vendors.currentDialectIfAvailable
import org.jetbrains.exposed.sql.vendors.h2Mode
import org.jetbrains.exposed.sql.vendors.inProperCase
import java.sql.DatabaseMetaData

Expand All @@ -29,7 +34,8 @@ enum class ReferenceOption {
CASCADE,
SET_NULL,
RESTRICT,
NO_ACTION;
NO_ACTION,
SET_DEFAULT;

override fun toString(): String = name.replace("_", " ")

Expand All @@ -40,6 +46,7 @@ enum class ReferenceOption {
DatabaseMetaData.importedKeySetNull -> SET_NULL
DatabaseMetaData.importedKeyRestrict -> RESTRICT
DatabaseMetaData.importedKeyNoAction -> NO_ACTION
DatabaseMetaData.importedKeySetDefault -> SET_DEFAULT
else -> currentDialect.defaultReferenceOption
}
}
Expand Down Expand Up @@ -108,18 +115,45 @@ data class ForeignKeyConstraint(
from.joinToString("_") { it.name }
}__${target.joinToString("_") { it.name }}"
).inProperCase()

internal val foreignKeyPart: String
get() = buildString {
if (fkName.isNotBlank()) {
append("CONSTRAINT $fkName ")
}
append("FOREIGN KEY ($fromColumns) REFERENCES $targetTableName($targetColumns)")
if (deleteRule != ReferenceOption.NO_ACTION) {
append(" ON DELETE $deleteRule")
if (deleteRule == ReferenceOption.SET_DEFAULT) {
when (currentDialect) {
is MariaDBDialect -> exposedLogger.warn(
"MariaDB doesn't support FOREIGN KEY with SET DEFAULT reference option with ON DELETE clause. " +
"Please check your $fromTableName table."
)
is MysqlDialect -> exposedLogger.warn(
"MySQL doesn't support FOREIGN KEY with SET DEFAULT reference option with ON DELETE clause. " +
"Please check your $fromTableName table."
)
else -> append(" ON DELETE $deleteRule")
}
} else {
append(" ON DELETE $deleteRule")
}
}
if (updateRule != ReferenceOption.NO_ACTION) {
if (currentDialect is OracleDialect || currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
exposedLogger.warn("Oracle doesn't support FOREIGN KEY with ON UPDATE clause. Please check your $fromTableName table.")
} else if (updateRule == ReferenceOption.SET_DEFAULT) {
when (currentDialect) {
is MariaDBDialect -> exposedLogger.warn(
"MariaDB doesn't support FOREIGN KEY with SET DEFAULT reference option with ON UPDATE clause. " +
"Please check your $fromTableName table."
)
is MysqlDialect -> exposedLogger.warn(
"MySQL doesn't support FOREIGN KEY with SET DEFAULT reference option with ON UPDATE clause. " +
"Please check your $fromTableName table."
)
else -> append(" ON UPDATE $updateRule")
}
} else {
append(" ON UPDATE $updateRule")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.jetbrains.exposed.sql.tests.shared

import org.jetbrains.exposed.sql.ReferenceOption
import org.jetbrains.exposed.sql.Table

object Category : Table("Category") {
val id = integer("id")
val name = varchar(name = "name", length = 20)

override val primaryKey = PrimaryKey(id)
}

const val DEFAULT_CATEGORY_ID = 0

object Item : Table("Item") {
val id = integer("id")
val name = varchar(name = "name", length = 20)
val categoryId = integer("categoryId")
.default(DEFAULT_CATEGORY_ID)
.references(
Category.id,
onDelete = ReferenceOption.SET_DEFAULT,
onUpdate = ReferenceOption.NO_ACTION
)

override val primaryKey = PrimaryKey(id)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.TestDB
import org.jetbrains.exposed.sql.tests.currentDialectTest
import org.jetbrains.exposed.sql.tests.inProperCase
import org.jetbrains.exposed.sql.tests.shared.Category
import org.jetbrains.exposed.sql.tests.shared.Item
import org.jetbrains.exposed.sql.tests.shared.assertEqualCollections
import org.jetbrains.exposed.sql.tests.shared.assertEquals
import org.jetbrains.exposed.sql.tests.shared.assertTrue
Expand Down Expand Up @@ -533,6 +535,7 @@ class CreateTableTests : DatabaseTestsBase() {
val parent = object : Table("parent2") {
val idA = integer("id_a")
val idB = integer("id_b")

init {
uniqueIndex(idA, idB)
}
Expand Down Expand Up @@ -572,6 +575,23 @@ class CreateTableTests : DatabaseTestsBase() {
}
}

@Test
fun createTableWithOnDeleteSetDefault() {
withDb(excludeSettings = listOf(TestDB.MARIADB, TestDB.MYSQL)) {
val expected = listOf(
"CREATE TABLE " + addIfNotExistsIfSupported() + "${this.identity(Item)} (" +
"${Item.columns.joinToString { it.descriptionDdl(false) }}," +
" CONSTRAINT ${"fk_Item_categoryId__id".inProperCase()}" +
" FOREIGN KEY (${this.identity(Item.categoryId)})" +
" REFERENCES ${this.identity(Category)}(${this.identity(Category.id)})" +
" ON DELETE SET DEFAULT" +
")"
)

assertEqualCollections(Item.ddl, expected)
}
}

object OneTable : IntIdTable("one")
object OneOneTable : IntIdTable("one.one")

Expand All @@ -598,7 +618,8 @@ class CreateTableTests : DatabaseTestsBase() {
}
}

@Test fun `create table with quoted name with camel case`() {
@Test
fun `create table with quoted name with camel case`() {
val testTable = object : IntIdTable("quotedTable") {
val int = integer("intColumn")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.jetbrains.exposed.sql.tests.sqlite

import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.TestDB
import org.jetbrains.exposed.sql.tests.shared.Category
import org.jetbrains.exposed.sql.tests.shared.DEFAULT_CATEGORY_ID
import org.jetbrains.exposed.sql.tests.shared.Item
import org.jetbrains.exposed.sql.tests.shared.assertEquals
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.Assume
import org.junit.Test

class ForeignKeyConstraintTests : DatabaseTestsBase() {

@Test
fun `test ON DELETE SET DEFAULT for databases that support it without SQLite`() {
withDb(excludeSettings = listOf(TestDB.MARIADB, TestDB.MYSQL, TestDB.SQLITE)) {
testOnDeleteSetDefault()
}
}

@Test
fun `test ON DELETE SET DEFAULT for SQLite`() {
Assume.assumeTrue(TestDB.SQLITE in TestDB.enabledInTests())

transaction(Database.connect("jdbc:sqlite:file:test?mode=memory&cache=shared&foreign_keys=on", user = "root", driver = "org.sqlite.JDBC")) {
testOnDeleteSetDefault()
}
}

private fun Transaction.testOnDeleteSetDefault() {
SchemaUtils.create(Category, Item)

Category.insert {
it[id] = DEFAULT_CATEGORY_ID
it[name] = "Default"
}

val saladsId = 1
Category.insert {
it[id] = saladsId
it[name] = "Salads"
}

val tabboulehId = 0
Item.insert {
it[id] = tabboulehId
it[name] = "Tabbouleh"
it[categoryId] = saladsId
}

assertEquals(
saladsId,
Item.select { Item.id eq tabboulehId }.single().also {
println("SELECT result = $it")
}[Item.categoryId]
)

Category.deleteWhere { id eq saladsId }

assertEquals(
DEFAULT_CATEGORY_ID,
Item.select { Item.id eq tabboulehId }.single()[Item.categoryId]
)
}
}

0 comments on commit 60d710c

Please sign in to comment.