Skip to content

Commit

Permalink
fix!: EXPOSED-360 Storing ULong.MAX_VALUE in ulong column not working
Browse files Browse the repository at this point in the history
Before this fix, the maximum value that could be stored with `ULongColumnType` for all database dialects was `Long.MAX_VALUE`. This is because the `ULong` value was being converted to `Long` in the `notNullValueToDB` function, restricting the range to that of `Long`. Instead, it is now converted to a `Long` only for PostgreSQL, and a `String` for the rest of the database dialects. For PostgreSQL, it is not possible to store a value greater than `Long.MAX_VALUE` at the moment. This is because it's not possible to have an auto-increment column of the same type NUMERIC(20), and having different types for `ulongType()` and `ulongAutoincType()` will cause a crash when one references the other. This will be solved in another pull request.

Breaking change: `ulongType()` is now NUMERIC(20) instead of BIGINT for H2, SQL Server, and SQLite, to allow storing the full range of `ULong`.
  • Loading branch information
joc-a committed Apr 30, 2024
1 parent ee954b3 commit 0742540
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 27 deletions.
1 change: 0 additions & 1 deletion exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2546,7 +2546,6 @@ public final class org/jetbrains/exposed/sql/ULongColumnType : org/jetbrains/exp
public fun <init> ()V
public synthetic fun notNullValueToDB (Ljava/lang/Object;)Ljava/lang/Object;
public fun notNullValueToDB-VKZWuLQ (J)Ljava/lang/Object;
public fun setParameter (Lorg/jetbrains/exposed/sql/statements/api/PreparedStatementApi;ILjava/lang/Object;)V
public fun sqlType ()Ljava/lang/String;
public synthetic fun valueFromDB (Ljava/lang/Object;)Ljava/lang/Object;
public fun valueFromDB-I7RO_PI (Ljava/lang/Object;)J
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ interface IColumnType<T> {
* [value] can be of any type (including [Expression])
* */
@Throws(IllegalArgumentException::class)
fun validateValueBeforeUpdate(value: T?) {}
fun validateValueBeforeUpdate(value: T?) {
}
}

/**
Expand Down Expand Up @@ -389,32 +390,20 @@ class ULongColumnType : ColumnType<ULong>() {
return when (value) {
is ULong -> value
is Long -> value.takeIf { it >= 0 }?.toULong()
is Double -> value.takeIf { it >= 0 }?.toULong() // For SQLite
is Number -> {
if (currentDialect is MysqlDialect) {
value.toString().toBigInteger().takeIf {
it >= "0".toBigInteger() && it <= ULong.MAX_VALUE.toString().toBigInteger()
}?.toString()?.toULong()
} else {
value.toLong().takeIf { it >= 0 }?.toULong()
}
value.toString().toBigInteger().takeIf {
it >= "0".toBigInteger() && it <= ULong.MAX_VALUE.toString().toBigInteger()
}?.toString()?.toULong()
}
is String -> value.toULong()
else -> error("Unexpected value of type Long: $value of ${value::class.qualifiedName}")
} ?: error("Negative value but type is ULong: $value")
}

override fun setParameter(stmt: PreparedStatementApi, index: Int, value: Any?) {
val v = when {
value is ULong && currentDialect is MysqlDialect -> value.toString()
value is ULong -> value.toLong()
else -> value
}
super.setParameter(stmt, index, v)
}

override fun notNullValueToDB(value: ULong) = when {
currentDialect is MysqlDialect -> value.toString()
else -> value.toLong()
override fun notNullValueToDB(value: ULong) = when (currentDialect) {
is PostgreSQLDialect -> value.toLong()
else -> value.toString()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ abstract class DataTypeProvider {
open fun longType(): String = "BIGINT"

/** Numeric type for storing 8-byte unsigned integers. */
open fun ulongType(): String = "BIGINT"
open fun ulongType(): String = "NUMERIC(20)"

/** Numeric type for storing 8-byte integers, and marked as auto-increment. */
open fun longAutoincType(): String = "BIGINT AUTO_INCREMENT"

/** Numeric type for storing 8-byte unsigned integers, marked as auto-increment. */
open fun ulongAutoincType(): String = "BIGINT AUTO_INCREMENT"
open fun ulongAutoincType(): String = "NUMERIC(20) AUTO_INCREMENT"

/** Numeric type for storing 4-byte (single precision) floating-point numbers. */
open fun floatType(): String = "FLOAT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import java.util.*
internal object PostgreSQLDataTypeProvider : DataTypeProvider() {
override fun byteType(): String = "SMALLINT"
override fun floatType(): String = "REAL"
override fun ulongType(): String = "BIGINT"
override fun integerAutoincType(): String = "SERIAL"
override fun longAutoincType(): String = "BIGSERIAL"
override fun ulongAutoincType(): String = "BIGSERIAL"
override fun uuidType(): String = "uuid"
override fun binaryType(): String = "bytea"
override fun binaryType(length: Int): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal object SQLServerDataTypeProvider : DataTypeProvider() {
}
override fun integerAutoincType(): String = "INT IDENTITY(1,1)"
override fun longAutoincType(): String = "BIGINT IDENTITY(1,1)"
override fun ulongAutoincType(): String = "NUMERIC(20) IDENTITY(1,1)"
override fun binaryType(): String {
exposedLogger.error("The length of the Binary column is missing.")
error("The length of the Binary column is missing.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import java.sql.Statement
internal object SQLiteDataTypeProvider : DataTypeProvider() {
override fun integerAutoincType(): String = "INTEGER PRIMARY KEY AUTOINCREMENT"
override fun longAutoincType(): String = "INTEGER PRIMARY KEY AUTOINCREMENT"
override fun ulongAutoincType(): String = "INTEGER PRIMARY KEY AUTOINCREMENT"
override fun floatType(): String = "SINGLE"
override fun binaryType(): String = "BLOB"
override fun dateTimeType(): String = "TEXT"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.jetbrains.exposed.sql.tests.shared.types

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.TestDB
import org.jetbrains.exposed.sql.tests.currentDialectTest
Expand Down Expand Up @@ -264,14 +267,20 @@ class UnsignedColumnTypeTests : DatabaseTestsBase() {

@Test
fun testULongColumnType() {
withTables(ULongTable) {
withTables(ULongTable) { testDb ->
val maxValue = if (testDb == TestDB.POSTGRESQL || testDb == TestDB.H2_PSQL) {
Long.MAX_VALUE.toULong()
} else {
ULong.MAX_VALUE
}

ULongTable.insert {
it[unsignedLong] = 123uL
it[unsignedLong] = maxValue
}

val result = ULongTable.selectAll().toList()
assertEquals(1, result.size)
assertEquals(123uL, result.single()[ULongTable.unsignedLong])
assertEquals(maxValue, result.single()[ULongTable.unsignedLong])
}
}

Expand Down

0 comments on commit 0742540

Please sign in to comment.