From 83c6c22b9b89b8f5b0fc7da3b975664b5a35ee98 Mon Sep 17 00:00:00 2001 From: Chantal Loncle <82039410+bog-walk@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:08:06 -0500 Subject: [PATCH] fix: EXPOSED-263 Null arg parameter in exec() throws if logger enabled The type for exec() parameterized arguments is declared as args: Iterable>, so passing null as the second element correctly sets the statement parameter to SQL NULL. But if a logger is enabled, this exception is thrown: java.lang.IllegalStateException: NULL in non-nullable column. This is because all IColumnType default to being not nullable and fail the check in `ColumnType.valueToString()` when attempting to parse the parameter value. This exception can be avoided by always providing SQL NULL directly, via Op.nullOp(). But given the type hints, the accepted parameter values should match what would be accepted by any other DSL statement. Even if the column in the Exposed table object is declared as being `nullable()`, the column type in `exec()` has no knowledge of this because it is meant to accept plain SQL using an anonymous Statement object. This fix sets the nullable parameter of any column type in args to true so that no exception will be thrown when the logger invokes valueToString(). --- .../org/jetbrains/exposed/sql/Transaction.kt | 6 +++++- .../sql/tests/shared/ParameterizationTests.kt | 18 +++++++++++++++++- .../sql/tests/shared/ddl/SequencesTests.kt | 1 - 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt index 596ba31270..730593125b 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt @@ -206,7 +206,11 @@ open class Transaction( override fun prepareSQL(transaction: Transaction, prepared: Boolean): String = stmt - override fun arguments(): Iterable>> = listOf(args) + override fun arguments(): Iterable>> = listOf( + args.map { (columnType, value) -> + columnType.apply { nullable = true } to value + } + ) }) } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt index 7f6e76512d..b4bedd4880 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt @@ -10,10 +10,11 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.junit.Assume import org.junit.Test import kotlin.test.assertNotNull +import kotlin.test.assertNull class ParameterizationTests : DatabaseTestsBase() { object TempTable : Table("tmp") { - val name = varchar("foo", 50) + val name = varchar("foo", 50).nullable() } private val supportMultipleStatements by lazy { @@ -146,4 +147,19 @@ class ParameterizationTests : DatabaseTestsBase() { TransactionManager.closeAndUnregister(db) } + + @Test + fun testNullParameterWithLogger() { + withTables(TempTable) { + // the logger is left in to test that it does not throw IllegalStateException with null parameter arg + addLogger(StdOutSqlLogger) + + exec( + stmt = "INSERT INTO ${TempTable.tableName} (${TempTable.name.name}) VALUES (?)", + args = listOf(VarCharColumnType() to null) + ) + + assertNull(TempTable.selectAll().single()[TempTable.name]) + } + } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/SequencesTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/SequencesTests.kt index 9263a04ffb..322a4d5e50 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/SequencesTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/SequencesTests.kt @@ -86,7 +86,6 @@ class SequencesTests : DatabaseTestsBase() { fun `test insert LongIdTable with auth-increment with sequence`() { withDb { if (currentDialectTest.supportsSequenceAsGeneratedKeys) { - addLogger(StdOutSqlLogger) try { SchemaUtils.create(DeveloperWithAutoIncrementBySequence) val developerId = DeveloperWithAutoIncrementBySequence.insertAndGetId {