Skip to content

Commit 8fa5fab

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 fc0ae76 commit 8fa5fab

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
@@ -44,6 +44,8 @@
4444
that is also type restricted to `Comparable` (for example, `avg()`) will also require defining a new function. In this event, please
4545
also leave a comment on [YouTrack](https://youtrack.jetbrains.com/issue/EXPOSED-577) with a use case so the original function signature
4646
can be potentially reassessed.
47+
* In H2 Oracle, the `long()` column now maps to data type `BIGINT` instead of `NUMBER(19)`.
48+
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.
4749

4850
## 0.55.0
4951
* 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
@@ -1698,6 +1698,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
16981698
append("IF NOT EXISTS ")
16991699
}
17001700
append(TransactionManager.current().identity(this@Table))
1701+
1702+
// Add CHECK constraint to Long columns in Oracle and SQLite.
1703+
// It is done here because special handling is necessary based on the dialect.
1704+
addLongColumnCheckConstraintIfNeeded()
1705+
17011706
if (columns.isNotEmpty()) {
17021707
columns.joinTo(this, prefix = " (") { column ->
17031708
column.descriptionDdl(false)
@@ -1739,7 +1744,8 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
17391744
}.let {
17401745
if (currentDialect !is SQLiteDialect && currentDialect !is OracleDialect) {
17411746
it.filterNot { (name, _) ->
1742-
name.startsWith("${generatedSignedCheckPrefix}integer")
1747+
name.startsWith("${generatedSignedCheckPrefix}integer") ||
1748+
name.startsWith("${generatedSignedCheckPrefix}long")
17431749
}
17441750
} else {
17451751
it
@@ -1764,6 +1770,34 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
17641770
return createAutoIncColumnSequence() + createTable + createConstraint
17651771
}
17661772

1773+
private fun addLongColumnCheckConstraintIfNeeded() {
1774+
if (currentDialect is OracleDialect || currentDialect is SQLiteDialect) {
1775+
columns.filter { it.columnType is LongColumnType }.forEach { column ->
1776+
val name = column.name
1777+
val checkName = "${generatedSignedCheckPrefix}long_$name"
1778+
if (checkConstraints.none { it.first == checkName }) {
1779+
column.check(checkName) {
1780+
if (currentDialect is SQLiteDialect) {
1781+
fun typeOf(value: String) = object : ExpressionWithColumnType<String>() {
1782+
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("typeof($value)") }
1783+
override val columnType: IColumnType<String> = TextColumnType()
1784+
}
1785+
1786+
val typeCondition = Expression.build { typeOf(name) eq stringLiteral("integer") }
1787+
if (column.columnType.nullable) {
1788+
column.isNull() or typeCondition
1789+
} else {
1790+
typeCondition
1791+
}
1792+
} else {
1793+
it.between(Long.MIN_VALUE, Long.MAX_VALUE)
1794+
}
1795+
}
1796+
}
1797+
}
1798+
}
1799+
}
1800+
17671801
private fun createAutoIncColumnSequence(): List<String> {
17681802
return autoIncColumn?.autoIncColumnType?.sequence?.createStatement().orEmpty()
17691803
}

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
@@ -210,8 +210,12 @@ class JodaTimeDefaultsTest : DatabaseTestsBase() {
210210
"${"t5".inProperCase()} $timeType${testTable.t5.constraintNamePart()} ${tLiteral.itOrNull()}, " +
211211
"${"t6".inProperCase()} $timeType${testTable.t6.constraintNamePart()} ${tLiteral.itOrNull()}" +
212212
when (testDb) {
213-
TestDB.SQLITE, TestDB.ORACLE ->
214-
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
213+
TestDB.SQLITE ->
214+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
215+
", CONSTRAINT chk_t_signed_long_l CHECK (typeof(l) = 'integer')"
216+
TestDB.ORACLE ->
217+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
218+
", CONSTRAINT chk_t_signed_long_l CHECK (L BETWEEN ${Long.MIN_VALUE} AND ${Long.MAX_VALUE})"
215219
else -> ""
216220
} +
217221
")"

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
@@ -271,8 +271,12 @@ class DefaultsTest : DatabaseTestsBase() {
271271
"${"t9".inProperCase()} $timeType${testTable.t9.constraintNamePart()} ${tLiteral.itOrNull()}, " +
272272
"${"t10".inProperCase()} $timeType${testTable.t10.constraintNamePart()} ${tLiteral.itOrNull()}" +
273273
when (testDb) {
274-
TestDB.SQLITE, TestDB.ORACLE ->
275-
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})"
274+
TestDB.SQLITE ->
275+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
276+
", CONSTRAINT chk_t_signed_long_l CHECK (typeof(l) = 'integer')"
277+
TestDB.ORACLE ->
278+
", CONSTRAINT chk_t_signed_integer_id CHECK (${"id".inProperCase()} BETWEEN ${Int.MIN_VALUE} AND ${Int.MAX_VALUE})" +
279+
", CONSTRAINT chk_t_signed_long_l CHECK (L BETWEEN ${Long.MIN_VALUE} AND ${Long.MAX_VALUE})"
276280
else -> ""
277281
} +
278282
")"

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
@@ -108,6 +108,37 @@ class NumericColumnTypesTests : DatabaseTestsBase() {
108108
}
109109
}
110110

111+
@Test
112+
fun testLongAcceptsOnlyAllowedRange() {
113+
val testTable = object : Table("test_table") {
114+
val long = long("long_column")
115+
}
116+
117+
withTables(testTable) { testDb ->
118+
val columnName = testTable.long.nameInDatabaseCase()
119+
val ddlEnding = when (testDb) {
120+
TestDB.SQLITE -> "CHECK (typeof($columnName) = 'integer'))"
121+
TestDB.ORACLE -> "CHECK ($columnName BETWEEN ${Long.MIN_VALUE} and ${Long.MAX_VALUE}))"
122+
else -> "($columnName ${testTable.long.columnType} NOT NULL)"
123+
}
124+
assertTrue(testTable.ddl.single().endsWith(ddlEnding, ignoreCase = true))
125+
126+
testTable.insert { it[long] = Long.MIN_VALUE }
127+
testTable.insert { it[long] = Long.MAX_VALUE }
128+
assertEquals(2, testTable.select(testTable.long).count())
129+
130+
val tableName = testTable.nameInDatabaseCase()
131+
assertFailAndRollback(message = "Out-of-range error (or CHECK constraint violation for SQLite & Oracle)") {
132+
val outOfRangeValue = Long.MIN_VALUE.toBigDecimal() - 1.toBigDecimal()
133+
exec("INSERT INTO $tableName ($columnName) VALUES ($outOfRangeValue)")
134+
}
135+
assertFailAndRollback(message = "Out-of-range error (or CHECK constraint violation for SQLite & Oracle)") {
136+
val outOfRangeValue = Long.MAX_VALUE.toBigDecimal() + 1.toBigDecimal()
137+
exec("INSERT INTO $tableName ($columnName) VALUES ($outOfRangeValue)")
138+
}
139+
}
140+
}
141+
111142
@Test
112143
fun testParams() {
113144
val testTable = object : Table("test_table") {

0 commit comments

Comments
 (0)