Skip to content

Commit d2cf30f

Browse files
committed
chore!: Change H2 Oracle longType and longAutoincType from NUMBER(19) to BIGINT and add CHECK constraint in Oracle and SQLite
1 parent b0a7aed commit d2cf30f

File tree

7 files changed

+92
-9
lines changed

7 files changed

+92
-9
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* In Oracle and H2 Oracle, the `uinteger()` column now maps to data type `NUMBER(10)` instead of `NUMBER(13)`.
99
* In Oracle and H2 Oracle, the `integer()` column now maps to data type `NUMBER(10)` and `INTEGER` respectively, instead of `NUMBER(12)`.
1010
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.
11+
* In H2 Oracle, the `long()` column now maps to data type `BIGINT` instead of `NUMBER(19)`.
12+
In Oracle and SQLite, using the long column in a table now also creates a CHECK constraint to ensure that no out-of-range values are inserted.
1113

1214
## 0.55.0
1315
* 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: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
16581658
append("IF NOT EXISTS ")
16591659
}
16601660
append(TransactionManager.current().identity(this@Table))
1661+
1662+
// Add CHECK constraint to Long columns in Oracle and SQLite.
1663+
// It is done here because special handling is necessary based on the dialect.
1664+
addLongColumnCheckConstraintIfNeeded()
1665+
16611666
if (columns.isNotEmpty()) {
16621667
columns.joinTo(this, prefix = " (") { column ->
16631668
column.descriptionDdl(false)
@@ -1698,7 +1703,8 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
16981703
}.let {
16991704
if (currentDialect !is SQLiteDialect && currentDialect !is OracleDialect) {
17001705
it.filterNot { (name, _) ->
1701-
name.startsWith("${generatedSignedCheckPrefix}integer")
1706+
name.startsWith("${generatedSignedCheckPrefix}integer") ||
1707+
name.startsWith("${generatedSignedCheckPrefix}long")
17021708
}
17031709
} else {
17041710
it
@@ -1723,6 +1729,34 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
17231729
return createAutoIncColumnSequence() + createTable + createConstraint
17241730
}
17251731

1732+
private fun addLongColumnCheckConstraintIfNeeded() {
1733+
if (currentDialect is OracleDialect || currentDialect is SQLiteDialect) {
1734+
columns.filter { it.columnType is LongColumnType }.forEach { column ->
1735+
val name = column.name
1736+
val checkName = "${generatedSignedCheckPrefix}long_$name"
1737+
if (checkConstraints.none { it.first == checkName }) {
1738+
column.check(checkName) {
1739+
if (currentDialect is SQLiteDialect) {
1740+
fun typeOf(value: String) = object : ExpressionWithColumnType<String>() {
1741+
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("typeof($value)") }
1742+
override val columnType: IColumnType<String> = TextColumnType()
1743+
}
1744+
1745+
val typeCondition = Expression.build { typeOf(name) eq stringLiteral("integer") }
1746+
if (column.columnType.nullable) {
1747+
column.isNull() or typeCondition
1748+
} else {
1749+
typeCondition
1750+
}
1751+
} else {
1752+
it.between(Long.MIN_VALUE, Long.MAX_VALUE)
1753+
}
1754+
}
1755+
}
1756+
}
1757+
}
1758+
}
1759+
17261760
private fun createAutoIncColumnSequence(): List<String> {
17271761
return autoIncColumn?.autoIncColumnType?.sequence?.createStatement().orEmpty()
17281762
}

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
@@ -33,8 +33,12 @@ internal object OracleDataTypeProvider : DataTypeProvider() {
3333
override fun integerAutoincType(): String = integerType()
3434
override fun uintegerType(): String = "NUMBER(10)"
3535
override fun uintegerAutoincType(): String = "NUMBER(10)"
36-
override fun longType(): String = "NUMBER(19)"
37-
override fun longAutoincType(): String = "NUMBER(19)"
36+
override fun longType(): String = if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
37+
"BIGINT"
38+
} else {
39+
"NUMBER(19)"
40+
}
41+
override fun longAutoincType(): String = longType()
3842
override fun ulongType(): String = "NUMBER(20)"
3943
override fun ulongAutoincType(): String = "NUMBER(20)"
4044
override fun varcharType(colLength: Int): String = "VARCHAR2($colLength CHAR)"

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,12 @@ class DefaultsTest : DatabaseTestsBase() {
273273
"${"t9".inProperCase()} $timeType${testTable.t9.constraintNamePart()} ${tLiteral.itOrNull()}, " +
274274
"${"t10".inProperCase()} $timeType${testTable.t10.constraintNamePart()} ${tLiteral.itOrNull()}" +
275275
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})"
276+
TestDB.SQLITE ->
277+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
278+
", CONSTRAINT chk_t_signed_long_l CHECK (typeof(l) = 'integer')"
279+
TestDB.ORACLE ->
280+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
281+
", CONSTRAINT chk_t_signed_long_l CHECK (L BETWEEN ${Long.MIN_VALUE} AND ${Long.MAX_VALUE})"
278282
else -> ""
279283
} +
280284
")"

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,12 @@ class JodaTimeDefaultsTest : DatabaseTestsBase() {
209209
"${"t5".inProperCase()} $timeType${testTable.t5.constraintNamePart()} ${tLiteral.itOrNull()}, " +
210210
"${"t6".inProperCase()} $timeType${testTable.t6.constraintNamePart()} ${tLiteral.itOrNull()}" +
211211
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})"
212+
TestDB.SQLITE ->
213+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
214+
", CONSTRAINT chk_t_signed_long_l CHECK (typeof(l) = 'integer')"
215+
TestDB.ORACLE ->
216+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
217+
", CONSTRAINT chk_t_signed_long_l CHECK (L BETWEEN ${Long.MIN_VALUE} AND ${Long.MAX_VALUE})"
214218
else -> ""
215219
} +
216220
")"

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,12 @@ class DefaultsTest : DatabaseTestsBase() {
270270
"${"t9".inProperCase()} $timeType${testTable.t9.constraintNamePart()} ${tLiteral.itOrNull()}, " +
271271
"${"t10".inProperCase()} $timeType${testTable.t10.constraintNamePart()} ${tLiteral.itOrNull()}" +
272272
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})"
273+
TestDB.SQLITE ->
274+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
275+
", CONSTRAINT chk_t_signed_long_l CHECK (typeof(l) = 'integer')"
276+
TestDB.ORACLE ->
277+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
278+
", CONSTRAINT chk_t_signed_long_l CHECK (L BETWEEN ${Long.MIN_VALUE} AND ${Long.MAX_VALUE})"
275279
else -> ""
276280
} +
277281
")"

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,35 @@ class NumericColumnTypesTests : DatabaseTestsBase() {
104104
}
105105
}
106106
}
107+
108+
@Test
109+
fun testLongAcceptsOnlyAllowedRange() {
110+
val testTable = object : Table("test_table") {
111+
val long = long("long_column")
112+
}
113+
114+
withTables(testTable) { testDb ->
115+
val columnName = testTable.long.nameInDatabaseCase()
116+
val ddlEnding = when (testDb) {
117+
TestDB.SQLITE -> "CHECK (typeof($columnName) = 'integer'))"
118+
TestDB.ORACLE -> "CHECK ($columnName BETWEEN ${Long.MIN_VALUE} and ${Long.MAX_VALUE}))"
119+
else -> "($columnName ${testTable.long.columnType} NOT NULL)"
120+
}
121+
assertTrue(testTable.ddl.single().endsWith(ddlEnding, ignoreCase = true))
122+
123+
testTable.insert { it[long] = Long.MIN_VALUE }
124+
testTable.insert { it[long] = Long.MAX_VALUE }
125+
assertEquals(2, testTable.select(testTable.long).count())
126+
127+
val tableName = testTable.nameInDatabaseCase()
128+
assertFailAndRollback(message = "Out-of-range error (or CHECK constraint violation for SQLite & Oracle)") {
129+
val outOfRangeValue = Long.MIN_VALUE.toBigDecimal() - 1.toBigDecimal()
130+
exec("INSERT INTO $tableName ($columnName) VALUES ($outOfRangeValue)")
131+
}
132+
assertFailAndRollback(message = "Out-of-range error (or CHECK constraint violation for SQLite & Oracle)") {
133+
val outOfRangeValue = Long.MAX_VALUE.toBigDecimal() + 1.toBigDecimal()
134+
exec("INSERT INTO $tableName ($columnName) VALUES ($outOfRangeValue)")
135+
}
136+
}
137+
}
107138
}

0 commit comments

Comments
 (0)