From 0742540dd2077af1d427448cf53c96d15e1d339d Mon Sep 17 00:00:00 2001 From: Jocelyne Date: Tue, 30 Apr 2024 18:03:49 +0200 Subject: [PATCH] fix!: EXPOSED-360 Storing ULong.MAX_VALUE in ulong column not working 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`. --- exposed-core/api/exposed-core.api | 1 - .../org/jetbrains/exposed/sql/ColumnType.kt | 29 ++++++------------- .../exposed/sql/vendors/DataTypeProvider.kt | 4 +-- .../exposed/sql/vendors/PostgreSQL.kt | 2 ++ .../exposed/sql/vendors/SQLServerDialect.kt | 1 + .../exposed/sql/vendors/SQLiteDialect.kt | 1 + .../shared/types/UnsignedColumnTypeTests.kt | 17 ++++++++--- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index d16b31f4f5..0c4ff09587 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -2546,7 +2546,6 @@ public final class org/jetbrains/exposed/sql/ULongColumnType : org/jetbrains/exp public fun ()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 diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt index feca48b108..497ef339ca 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt @@ -94,7 +94,8 @@ interface IColumnType { * [value] can be of any type (including [Expression]) * */ @Throws(IllegalArgumentException::class) - fun validateValueBeforeUpdate(value: T?) {} + fun validateValueBeforeUpdate(value: T?) { + } } /** @@ -389,32 +390,20 @@ class ULongColumnType : ColumnType() { 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() } } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt index b42fd24a1a..6f38ecc71b 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt @@ -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" diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt index c21edbefb7..de55ddbb37 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt @@ -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 { diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt index 20fb23cf6d..588101089d 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt @@ -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.") diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt index 1466e94be3..9c5a9927fa 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt @@ -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" diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/UnsignedColumnTypeTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/UnsignedColumnTypeTests.kt index 4bad6bbc4f..1b44859797 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/UnsignedColumnTypeTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/UnsignedColumnTypeTests.kt @@ -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 @@ -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]) } }