Skip to content

Commit 58d96f0

Browse files
authored
chore!: Change Oracle and H2 Oracle integerType and integerAutoincType from NUMBER(12) to NUMBER(10) and INTEGER respectively and add CHECK constraint in SQLite (#2270)
1 parent 9697dbc commit 58d96f0

File tree

9 files changed

+176
-26
lines changed

9 files changed

+176
-26
lines changed

documentation-website/Writerside/topics/Breaking-Changes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* In Oracle and H2 Oracle, the `ubyte()` column now maps to data type `NUMBER(3)` instead of `NUMBER(4)`.
77
* In Oracle and H2 Oracle, the `ushort()` column now maps to data type `NUMBER(5)` instead of `NUMBER(6)`.
88
* In Oracle and H2 Oracle, the `uinteger()` column now maps to data type `NUMBER(10)` instead of `NUMBER(13)`.
9+
* In Oracle and H2 Oracle, the `integer()` column now maps to data type `NUMBER(10)` and `INTEGER` respectively, instead of `NUMBER(12)`.
10+
In Oracle and SQLite, using the integer column in a table now also creates a CHECK constraint to ensure that no out-of-range values are inserted.
911

1012
## 0.55.0
1113
* The `DeleteStatement` property `table` is now deprecated in favor of `targetsSet`, which holds a `ColumnSet` that may be a `Table` or `Join`.

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ class Join(
427427
*
428428
* @param name Table name, by default name will be resolved from a class name with "Table" suffix removed (if present)
429429
*/
430-
@Suppress("TooManyFunctions")
430+
@Suppress("TooManyFunctions", "LargeClass")
431431
open class Table(name: String = "") : ColumnSet(), DdlAware {
432432
/** Returns the table name. */
433433
open val tableName: String = when {
@@ -509,8 +509,21 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
509509

510510
private val checkConstraints = mutableListOf<Pair<String, Op<Boolean>>>()
511511

512-
private val generatedUnsignedCheckPrefix = "chk_${tableName}_unsigned_"
513-
private val generatedSignedCheckPrefix = "chk_${tableName}_signed_"
512+
private val generatedUnsignedCheckPrefix
513+
get() = "chk_${
514+
tableName.replace("\"", "")
515+
.replace("'", "")
516+
.replace("`", "")
517+
.replace('.', '_')
518+
}_unsigned_"
519+
520+
private val generatedSignedCheckPrefix
521+
get() = "chk_${
522+
tableName.replace("\"", "")
523+
.replace("'", "")
524+
.replace("`", "")
525+
.replace('.', '_')
526+
}_signed_"
514527

515528
/**
516529
* Returns the table name in proper case.
@@ -689,7 +702,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
689702

690703
/** Creates a numeric column, with the specified [name], for storing 1-byte integers. */
691704
fun byte(name: String): Column<Byte> = registerColumn(name, ByteColumnType()).apply {
692-
check("${generatedSignedCheckPrefix}byte_$name") { it.between(Byte.MIN_VALUE, Byte.MAX_VALUE) }
705+
check("${generatedSignedCheckPrefix}byte_${this.unquotedName()}") { it.between(Byte.MIN_VALUE, Byte.MAX_VALUE) }
693706
}
694707

695708
/** Creates a numeric column, with the specified [name], for storing 1-byte unsigned integers.
@@ -699,12 +712,12 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
699712
* between 0 and [UByte.MAX_VALUE] inclusive.
700713
*/
701714
fun ubyte(name: String): Column<UByte> = registerColumn(name, UByteColumnType()).apply {
702-
check("${generatedUnsignedCheckPrefix}byte_$name") { it.between(0u, UByte.MAX_VALUE) }
715+
check("${generatedUnsignedCheckPrefix}byte_${this.unquotedName()}") { it.between(0u, UByte.MAX_VALUE) }
703716
}
704717

705718
/** Creates a numeric column, with the specified [name], for storing 2-byte integers. */
706719
fun short(name: String): Column<Short> = registerColumn(name, ShortColumnType()).apply {
707-
check("${generatedSignedCheckPrefix}short_$name") { it.between(Short.MIN_VALUE, Short.MAX_VALUE) }
720+
check("${generatedSignedCheckPrefix}short_${this.unquotedName()}") { it.between(Short.MIN_VALUE, Short.MAX_VALUE) }
708721
}
709722

710723
/** Creates a numeric column, with the specified [name], for storing 2-byte unsigned integers.
@@ -713,11 +726,13 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
713726
* integer type with a check constraint that ensures storage of only values between 0 and [UShort.MAX_VALUE] inclusive.
714727
*/
715728
fun ushort(name: String): Column<UShort> = registerColumn(name, UShortColumnType()).apply {
716-
check("$generatedUnsignedCheckPrefix$name") { it.between(0u, UShort.MAX_VALUE) }
729+
check("$generatedUnsignedCheckPrefix${this.unquotedName()}") { it.between(0u, UShort.MAX_VALUE) }
717730
}
718731

719732
/** Creates a numeric column, with the specified [name], for storing 4-byte integers. */
720-
fun integer(name: String): Column<Int> = registerColumn(name, IntegerColumnType())
733+
fun integer(name: String): Column<Int> = registerColumn(name, IntegerColumnType()).apply {
734+
check("${generatedSignedCheckPrefix}integer_${this.unquotedName()}") { it.between(Int.MIN_VALUE, Int.MAX_VALUE) }
735+
}
721736

722737
/** Creates a numeric column, with the specified [name], for storing 4-byte unsigned integers.
723738
*
@@ -726,7 +741,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
726741
* between 0 and [UInt.MAX_VALUE] inclusive.
727742
*/
728743
fun uinteger(name: String): Column<UInt> = registerColumn(name, UIntegerColumnType()).apply {
729-
check("$generatedUnsignedCheckPrefix$name") { it.between(0u, UInt.MAX_VALUE) }
744+
check("$generatedUnsignedCheckPrefix${this.unquotedName()}") { it.between(0u, UInt.MAX_VALUE) }
730745
}
731746

732747
/** Creates a numeric column, with the specified [name], for storing 8-byte integers. */
@@ -1680,6 +1695,14 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
16801695
}
16811696
}
16821697
else -> checkConstraints
1698+
}.let {
1699+
if (currentDialect !is SQLiteDialect && currentDialect !is OracleDialect) {
1700+
it.filterNot { (name, _) ->
1701+
name.startsWith("${generatedSignedCheckPrefix}integer")
1702+
}
1703+
} else {
1704+
it
1705+
}
16831706
}.ifEmpty { null }
16841707
filteredChecks?.mapIndexed { index, (name, op) ->
16851708
val resolvedName = name.ifBlank { "check_${tableName}_$index" }
@@ -1764,3 +1787,5 @@ internal fun fallbackSequenceName(tableName: String, columnName: String): String
17641787
val q = if (tableName.contains('.')) "\"" else ""
17651788
return "$q${tableName.replace("\"", "")}_${columnName}_seq$q"
17661789
}
1790+
1791+
private fun <T> Column<T>.unquotedName() = name.trim('\"', '\'', '`')

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ internal object OracleDataTypeProvider : DataTypeProvider() {
2525
"NUMBER(5)"
2626
}
2727
override fun ushortType(): String = "NUMBER(5)"
28-
override fun integerType(): String = "NUMBER(12)"
29-
override fun integerAutoincType(): String = "NUMBER(12)"
28+
override fun integerType(): String = if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
29+
"INTEGER"
30+
} else {
31+
"NUMBER(10)"
32+
}
33+
override fun integerAutoincType(): String = integerType()
3034
override fun uintegerType(): String = "NUMBER(10)"
3135
override fun uintegerAutoincType(): String = "NUMBER(10)"
3236
override fun longType(): String = "NUMBER(19)"

exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DefaultsTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ class DefaultsTest : DatabaseTestsBase() {
272272
"${"t8".inProperCase()} $longType${testTable.t8.constraintNamePart()} ${durLiteral.itOrNull()}, " +
273273
"${"t9".inProperCase()} $timeType${testTable.t9.constraintNamePart()} ${tLiteral.itOrNull()}, " +
274274
"${"t10".inProperCase()} $timeType${testTable.t10.constraintNamePart()} ${tLiteral.itOrNull()}" +
275+
when (testDb) {
276+
TestDB.SQLITE, TestDB.ORACLE ->
277+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
278+
else -> ""
279+
} +
275280
")"
276281

277282
val expected = if (currentDialectTest is OracleDialect || currentDialectTest.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
@@ -427,6 +432,11 @@ class DefaultsTest : DatabaseTestsBase() {
427432
"${"t1".inProperCase()} $timestampWithTimeZoneType${testTable.t1.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
428433
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
429434
"${"t3".inProperCase()} $timestampWithTimeZoneType${testTable.t3.constraintNamePart()} ${CurrentTimestampWithTimeZone.itOrNull()}" +
435+
when (testDb) {
436+
TestDB.SQLITE, TestDB.ORACLE ->
437+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
438+
else -> ""
439+
} +
430440
")"
431441

432442
val expected = if (currentDialectTest is OracleDialect ||

exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeDefaultsTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ class JodaTimeDefaultsTest : DatabaseTestsBase() {
208208
"${"t4".inProperCase()} $dType${testTable.t4.constraintNamePart()} ${dLiteral.itOrNull()}, " +
209209
"${"t5".inProperCase()} $timeType${testTable.t5.constraintNamePart()} ${tLiteral.itOrNull()}, " +
210210
"${"t6".inProperCase()} $timeType${testTable.t6.constraintNamePart()} ${tLiteral.itOrNull()}" +
211+
when (testDb) {
212+
TestDB.SQLITE, TestDB.ORACLE ->
213+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
214+
else -> ""
215+
} +
211216
")"
212217

213218
val expected = if (currentDialectTest is OracleDialect || currentDialectTest.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
@@ -413,6 +418,11 @@ class JodaTimeDefaultsTest : DatabaseTestsBase() {
413418
"${"t1".inProperCase()} $timestampWithTimeZoneType${testTable.t1.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
414419
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
415420
"${"t3".inProperCase()} $timestampWithTimeZoneType${testTable.t3.constraintNamePart()} ${CurrentDateTime.itOrNull()}" +
421+
when (testDb) {
422+
TestDB.SQLITE, TestDB.ORACLE ->
423+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
424+
else -> ""
425+
} +
416426
")"
417427

418428
val expected = if (currentDialectTest is OracleDialect ||

exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/DefaultsTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ class DefaultsTest : DatabaseTestsBase() {
269269
"${"t8".inProperCase()} $longType${testTable.t8.constraintNamePart()} ${durLiteral.itOrNull()}, " +
270270
"${"t9".inProperCase()} $timeType${testTable.t9.constraintNamePart()} ${tLiteral.itOrNull()}, " +
271271
"${"t10".inProperCase()} $timeType${testTable.t10.constraintNamePart()} ${tLiteral.itOrNull()}" +
272+
when (testDb) {
273+
TestDB.SQLITE, TestDB.ORACLE ->
274+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
275+
else -> ""
276+
} +
272277
")"
273278

274279
val expected = if (currentDialectTest is OracleDialect || currentDialectTest.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
@@ -430,6 +435,11 @@ class DefaultsTest : DatabaseTestsBase() {
430435
"${"t1".inProperCase()} $timestampWithTimeZoneType${testTable.t1.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
431436
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
432437
"${"t3".inProperCase()} $timestampWithTimeZoneType${testTable.t3.constraintNamePart()} ${CurrentTimestampWithTimeZone.itOrNull()}" +
438+
when (testDb) {
439+
TestDB.SQLITE, TestDB.ORACLE ->
440+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
441+
else -> ""
442+
} +
433443
")"
434444

435445
val expected = if (currentDialectTest is OracleDialect ||

exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class DDLTests : DatabaseTestsBase() {
118118
val constraint = varchar(keywords[3], 32)
119119
}
120120

121-
withDb {
121+
withDb { testDb ->
122122
assertTrue(db.config.preserveKeywordCasing)
123123

124124
SchemaUtils.create(keywordTable)
@@ -134,7 +134,13 @@ class DDLTests : DatabaseTestsBase() {
134134
val expectedCreate = "CREATE TABLE ${addIfNotExistsIfSupported()}$tableName (" +
135135
"$publicName ${keywordTable.public.columnType.sqlType()} NOT NULL, " +
136136
"$dataName ${keywordTable.data.columnType.sqlType()} NOT NULL, " +
137-
"$constraintName ${keywordTable.constraint.columnType.sqlType()} NOT NULL)"
137+
"$constraintName ${keywordTable.constraint.columnType.sqlType()} NOT NULL" +
138+
when (testDb) {
139+
TestDB.SQLITE, TestDB.ORACLE ->
140+
""", CONSTRAINT chk_data_signed_integer_key CHECK ("key" BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"""
141+
else -> ""
142+
} +
143+
")"
138144
assertEquals(expectedCreate, keywordTable.ddl.single())
139145

140146
// check that insert and select statement identifiers also match in DB without throwing SQLException
@@ -167,7 +173,7 @@ class DDLTests : DatabaseTestsBase() {
167173

168174
@Test
169175
fun unnamedTableWithQuotesSQL() {
170-
withTables(excludeSettings = listOf(TestDB.SQLITE), tables = arrayOf(unnamedTable)) {
176+
withTables(excludeSettings = listOf(TestDB.SQLITE), tables = arrayOf(unnamedTable)) { testDb ->
171177
val q = db.identifierManager.quoteString
172178
val tableName = if (currentDialectTest.needsQuotesWhenSymbolsInNames) {
173179
"$q${"unnamedTable$1".inProperCase()}$q"
@@ -178,7 +184,12 @@ class DDLTests : DatabaseTestsBase() {
178184
val varCharType = currentDialectTest.dataTypeProvider.varcharType(42)
179185
assertEquals(
180186
"CREATE TABLE " + addIfNotExistsIfSupported() + "$tableName " +
181-
"(${"id".inProperCase()} $integerType PRIMARY KEY, $q${"name".inProperCase()}$q $varCharType NOT NULL)",
187+
"(${"id".inProperCase()} $integerType PRIMARY KEY, $q${"name".inProperCase()}$q $varCharType NOT NULL" +
188+
when (testDb) {
189+
TestDB.ORACLE -> ", CONSTRAINT chk_unnamedTable$1_signed_integer_id CHECK (ID BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
190+
else -> ""
191+
} +
192+
")",
182193
unnamedTable.ddl
183194
)
184195
}
@@ -197,7 +208,8 @@ class DDLTests : DatabaseTestsBase() {
197208
val varCharType = currentDialectTest.dataTypeProvider.varcharType(42)
198209
assertEquals(
199210
"CREATE TABLE " + addIfNotExistsIfSupported() + "$tableName " +
200-
"(${"id".inProperCase()} $integerType NOT NULL PRIMARY KEY, $q${"name".inProperCase()}$q $varCharType NOT NULL)",
211+
"(${"id".inProperCase()} $integerType NOT NULL PRIMARY KEY, $q${"name".inProperCase()}$q $varCharType NOT NULL," +
212+
""" CONSTRAINT "chk_unnamedTable$1_signed_integer_id" CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE}))""",
201213
unnamedTable.ddl
202214
)
203215
}
@@ -244,16 +256,22 @@ class DDLTests : DatabaseTestsBase() {
244256
override val primaryKey = PrimaryKey(id, name)
245257
}
246258

247-
withTables(excludeSettings = listOf(TestDB.MYSQL_V5, TestDB.SQLITE), tables = arrayOf(testTable)) {
259+
withTables(excludeSettings = listOf(TestDB.MYSQL_V5, TestDB.SQLITE), tables = arrayOf(testTable)) { testDb ->
248260
val q = db.identifierManager.quoteString
249261
val varCharType = currentDialectTest.dataTypeProvider.varcharType(42)
250262
val tableDescription = "CREATE TABLE " + addIfNotExistsIfSupported() + "with_different_column_types".inProperCase()
251263
val idDescription = "${"id".inProperCase()} ${currentDialectTest.dataTypeProvider.integerType()}"
252264
val nameDescription = "$q${"name".inProperCase()}$q $varCharType"
253265
val ageDescription = "${"age".inProperCase()} ${db.dialect.dataTypeProvider.integerType()} NULL"
254-
val constraint = "CONSTRAINT pk_with_different_column_types PRIMARY KEY (${"id".inProperCase()}, $q${"name".inProperCase()}$q)"
266+
val primaryKeyConstraint = "CONSTRAINT pk_with_different_column_types PRIMARY KEY (${"id".inProperCase()}, $q${"name".inProperCase()}$q)"
267+
val checkConstraint = when (testDb) {
268+
TestDB.ORACLE ->
269+
", CONSTRAINT chk_with_different_column_types_signed_integer_id CHECK (ID BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
270+
", CONSTRAINT chk_with_different_column_types_signed_integer_age CHECK (AGE BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
271+
else -> ""
272+
}
255273

256-
assertEquals("$tableDescription ($idDescription, $nameDescription, $ageDescription, $constraint)", testTable.ddl)
274+
assertEquals("$tableDescription ($idDescription, $nameDescription, $ageDescription, $primaryKeyConstraint$checkConstraint)", testTable.ddl)
257275
}
258276
}
259277

@@ -276,7 +294,12 @@ class DDLTests : DatabaseTestsBase() {
276294
val ageDescription = "${"age".inProperCase()} ${db.dialect.dataTypeProvider.integerType()} NULL"
277295
val constraint = "CONSTRAINT pk_with_different_column_types PRIMARY KEY (${"id".inProperCase()}, $q${"name".inProperCase()}$q)"
278296

279-
assertEquals("$tableDescription ($idDescription, $nameDescription, $ageDescription, $constraint)", testTable.ddl)
297+
assertEquals(
298+
"$tableDescription ($idDescription, $nameDescription, $ageDescription, $constraint," +
299+
" CONSTRAINT chk_with_different_column_types_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})," +
300+
" CONSTRAINT chk_with_different_column_types_signed_integer_age CHECK (${"age".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE}))",
301+
testTable.ddl
302+
)
280303
}
281304
}
282305

0 commit comments

Comments
 (0)