Skip to content

Commit

Permalink
fix: EXPOSED-178 DELETE_RULE read incorrectly for Oracle (#1868)
Browse files Browse the repository at this point in the history
Oracle returns CASCADE=0, SET_NULL=2, and NO_ACTION=1
`decode (f.delete_rule, 'CASCADE', 0, 'SET NULL', 2, 1) as delete_rule`

So when it returns 1, which in the current code corresponds to DatabaseMetaData.importedKeyRestrict, it is mapped to RESTRICT, which is not supported in Oracle. Instead of that, it will now be mapped to NO_ACTION.

The resolveRefOptionFromJdbc function was moved from Constraints.kt to DatabaseDialect.kt.
  • Loading branch information
joc-a authored Sep 25, 2023
1 parent a9e6456 commit 3b4402a
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 25 deletions.
9 changes: 4 additions & 5 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,6 @@ public final class org/jetbrains/exposed/sql/Rank : org/jetbrains/exposed/sql/Wi

public final class org/jetbrains/exposed/sql/ReferenceOption : java/lang/Enum {
public static final field CASCADE Lorg/jetbrains/exposed/sql/ReferenceOption;
public static final field Companion Lorg/jetbrains/exposed/sql/ReferenceOption$Companion;
public static final field NO_ACTION Lorg/jetbrains/exposed/sql/ReferenceOption;
public static final field RESTRICT Lorg/jetbrains/exposed/sql/ReferenceOption;
public static final field SET_DEFAULT Lorg/jetbrains/exposed/sql/ReferenceOption;
Expand All @@ -1687,10 +1686,6 @@ public final class org/jetbrains/exposed/sql/ReferenceOption : java/lang/Enum {
public static fun values ()[Lorg/jetbrains/exposed/sql/ReferenceOption;
}

public final class org/jetbrains/exposed/sql/ReferenceOption$Companion {
public final fun resolveRefOptionFromJdbc (I)Lorg/jetbrains/exposed/sql/ReferenceOption;
}

public final class org/jetbrains/exposed/sql/RegexpOp : org/jetbrains/exposed/sql/Op, org/jetbrains/exposed/sql/ComplexExpression, org/jetbrains/exposed/sql/Op$OpBoolean {
public fun <init> (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/Expression;Z)V
public final fun getCaseSensitive ()Z
Expand Down Expand Up @@ -3265,6 +3260,7 @@ public abstract interface class org/jetbrains/exposed/sql/vendors/DatabaseDialec
public abstract fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List;
public abstract fun resetCaches ()V
public abstract fun resetSchemaCaches ()V
public abstract fun resolveRefOptionFromJdbc (I)Lorg/jetbrains/exposed/sql/ReferenceOption;
public abstract fun schemaExists (Lorg/jetbrains/exposed/sql/Schema;)Z
public abstract fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
public abstract fun supportsSelectForUpdate ()Z
Expand Down Expand Up @@ -3305,6 +3301,7 @@ public final class org/jetbrains/exposed/sql/vendors/DatabaseDialect$DefaultImpl
public static fun getSupportsWindowFrameGroupsMode (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;)Z
public static fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;Lorg/jetbrains/exposed/sql/Expression;)Z
public static fun listDatabases (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;)Ljava/lang/String;
public static fun resolveRefOptionFromJdbc (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;I)Lorg/jetbrains/exposed/sql/ReferenceOption;
public static fun setSchema (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
public static fun tableColumns (Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect;[Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map;
}
Expand Down Expand Up @@ -3564,6 +3561,7 @@ public class org/jetbrains/exposed/sql/vendors/OracleDialect : org/jetbrains/exp
public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z
public fun listDatabases ()Ljava/lang/String;
public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List;
public fun resolveRefOptionFromJdbc (I)Lorg/jetbrains/exposed/sql/ReferenceOption;
public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
}

Expand Down Expand Up @@ -3710,6 +3708,7 @@ public abstract class org/jetbrains/exposed/sql/vendors/VendorDialect : org/jetb
protected final fun quoteIdentifierWhenWrongCaseOrNecessary (Ljava/lang/String;Lorg/jetbrains/exposed/sql/Transaction;)Ljava/lang/String;
public fun resetCaches ()V
public fun resetSchemaCaches ()V
public fun resolveRefOptionFromJdbc (I)Lorg/jetbrains/exposed/sql/ReferenceOption;
public fun schemaExists (Lorg/jetbrains/exposed/sql/Schema;)Z
public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
public fun supportsSelectForUpdate ()Z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.jetbrains.exposed.sql.vendors.SQLiteDialect
import org.jetbrains.exposed.sql.vendors.currentDialect
import org.jetbrains.exposed.sql.vendors.currentDialectIfAvailable
import org.jetbrains.exposed.sql.vendors.inProperCase
import java.sql.DatabaseMetaData

/**
* Common interface for database objects that can be created, modified and dropped.
Expand Down Expand Up @@ -36,18 +35,6 @@ enum class ReferenceOption {
SET_DEFAULT;

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

companion object {
/** Returns the corresponding [ReferenceOption] for the specified [refOption] from JDBC. */
fun resolveRefOptionFromJdbc(refOption: Int): ReferenceOption = when (refOption) {
DatabaseMetaData.importedKeyCascade -> CASCADE
DatabaseMetaData.importedKeySetNull -> SET_NULL
DatabaseMetaData.importedKeyRestrict -> RESTRICT
DatabaseMetaData.importedKeyNoAction -> NO_ACTION
DatabaseMetaData.importedKeySetDefault -> SET_DEFAULT
else -> currentDialect.defaultReferenceOption
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jetbrains.exposed.sql.vendors

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.sql.DatabaseMetaData

/**
* Common interface for all database dialects.
Expand Down Expand Up @@ -145,6 +146,16 @@ interface DatabaseDialect {
}
}

/** Returns the corresponding [ReferenceOption] for the specified [refOption] from JDBC. */
fun resolveRefOptionFromJdbc(refOption: Int): ReferenceOption = when (refOption) {
DatabaseMetaData.importedKeyCascade -> ReferenceOption.CASCADE
DatabaseMetaData.importedKeySetNull -> ReferenceOption.SET_NULL
DatabaseMetaData.importedKeyRestrict -> ReferenceOption.RESTRICT
DatabaseMetaData.importedKeyNoAction -> ReferenceOption.NO_ACTION
DatabaseMetaData.importedKeySetDefault -> ReferenceOption.SET_DEFAULT
else -> currentDialect.defaultReferenceOption
}

companion object {
private val defaultLikePatternSpecialChars = mapOf('%' to null, '_' to null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.jetbrains.exposed.sql.vendors
import org.jetbrains.exposed.exceptions.throwUnsupportedException
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.sql.DatabaseMetaData
import java.util.*

internal object OracleDataTypeProvider : DataTypeProvider() {
Expand Down Expand Up @@ -366,5 +367,16 @@ open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, Or
}
}

/**
* The SQL that gets the constraint information for Oracle returns a 1 for NO ACTION and does not support RESTRICT.
* `decode (f.delete_rule, 'CASCADE', 0, 'SET NULL', 2, 1) as delete_rule`
*/
override fun resolveRefOptionFromJdbc(refOption: Int): ReferenceOption = when (refOption) {
DatabaseMetaData.importedKeyCascade -> ReferenceOption.CASCADE
DatabaseMetaData.importedKeySetNull -> ReferenceOption.SET_NULL
DatabaseMetaData.importedKeyRestrict -> ReferenceOption.NO_ACTION
else -> currentDialect.defaultReferenceOption
}

companion object : DialectNameProvider("Oracle")
}
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData)
identifierManager.quoteIdentifierWhenWrongCaseOrNecessary(it.nameInDatabaseCase()) == targetColumnName
} ?: return@iterate null // Do not crash if there are missing fields in Exposed's tables
val constraintUpdateRule = getObject("UPDATE_RULE")?.toString()?.toIntOrNull()?.let {
ReferenceOption.resolveRefOptionFromJdbc(it)
currentDialect.resolveRefOptionFromJdbc(it)
}
val constraintDeleteRule = ReferenceOption.resolveRefOptionFromJdbc(getInt("DELETE_RULE"))
val constraintDeleteRule = currentDialect.resolveRefOptionFromJdbc(getInt("DELETE_RULE"))
ForeignKeyConstraint(
target = targetColumn,
from = fromColumn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class ForeignKeyConstraintTests : DatabaseTestsBase() {
}

@Test
fun testUpdateRuleReadCorrectlyWhenNotSpecifiedInChildTable() {
fun testUpdateAndDeleteRulesReadCorrectlyWhenNotSpecifiedInChildTable() {
val category = object : Table("Category") {
val id = integer("id")

Expand All @@ -193,9 +193,14 @@ class ForeignKeyConstraintTests : DatabaseTestsBase() {
constraints.values.forEach { list ->
list.forEach {
when (testDb) {
TestDB.H2_ORACLE, TestDB.H2_SQLSERVER ->
TestDB.H2_ORACLE, TestDB.H2_SQLSERVER -> {
assertEquals(ReferenceOption.RESTRICT, it.updateRule)
else -> assertEquals(currentDialectTest.defaultReferenceOption, it.updateRule)
assertEquals(ReferenceOption.RESTRICT, it.deleteRule)
}
else -> {
assertEquals(currentDialectTest.defaultReferenceOption, it.updateRule)
assertEquals(currentDialectTest.defaultReferenceOption, it.deleteRule)
}
}
}
}
Expand All @@ -204,7 +209,7 @@ class ForeignKeyConstraintTests : DatabaseTestsBase() {
}

@Test
fun testUpdateRuleReadCorrectlyWhenSpecifiedInChildTable() {
fun testUpdateAndDeleteRulesReadCorrectlyWhenSpecifiedInChildTable() {
val category = object : Table("Category") {
val id = integer("id")

Expand All @@ -213,7 +218,12 @@ class ForeignKeyConstraintTests : DatabaseTestsBase() {

val item = object : Table("Item") {
val id = integer("id")
val categoryId = integer("categoryId").references(category.id, onUpdate = ReferenceOption.CASCADE)
val categoryId = integer("categoryId")
.references(
category.id,
onUpdate = ReferenceOption.CASCADE,
onDelete = ReferenceOption.CASCADE
)

override val primaryKey = PrimaryKey(id)
}
Expand All @@ -226,6 +236,7 @@ class ForeignKeyConstraintTests : DatabaseTestsBase() {
constraints.values.forEach { list ->
list.forEach {
assertEquals(ReferenceOption.CASCADE, it.updateRule)
assertEquals(ReferenceOption.CASCADE, it.deleteRule)
}
}
}
Expand Down

0 comments on commit 3b4402a

Please sign in to comment.