From 36e4cf63f42b01b0a1b19e657eb1998006232e5f Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sat, 19 Jun 2021 17:46:39 +0300 Subject: [PATCH] Support update { it[column] = nullableValue } Key change is UpdateBuilder#setWithEntityIdValue, which now accepts column: Column?>, value: S? previous signature was column: Column>, value: S? A downside is that "[optionalReferenceColumn] = null" can't decide between "null as ColumnType?" and "null as EntityID?" update { // it[optionalReferenceColumn] = null // <- does not work // Workaround: specify null type explicitly to call the proper overload it[optionalReferenceColumn] = null as EntityID? // The following works as well: // it[optionalReferenceColumn] = null as ColumnType? } fixes #1275 --- .../sql/statements/BatchUpdateStatement.kt | 2 +- .../exposed/sql/statements/UpdateBuilder.kt | 10 ++--- .../exposed/sql/tests/shared/DDLTests.kt | 12 +++++- .../sql/tests/shared/dml/InsertTests.kt | 40 +++++++++++-------- .../sql/tests/shared/entities/EntityTests.kt | 20 ++++++++++ 5 files changed, 61 insertions(+), 23 deletions(-) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchUpdateStatement.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchUpdateStatement.kt index 8dbcf285bd..3efc91457c 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchUpdateStatement.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchUpdateStatement.kt @@ -33,7 +33,7 @@ open class BatchUpdateStatement(val table: IdTable<*>) : UpdateStatement(table, data.add(id to values) } - override fun update(column: Column, value: Expression) = error("Expressions unsupported in batch update") + override fun update(column: Column, value: Expression) = error("Expressions unsupported in batch update") override fun prepareSQL(transaction: Transaction): String = "${super.prepareSQL(transaction)} WHERE ${transaction.identity(table.id)} = ?" diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateBuilder.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateBuilder.kt index 5577b01274..3e21b4d611 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateBuilder.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateBuilder.kt @@ -24,32 +24,32 @@ abstract class UpdateBuilder(type: StatementType, targets: List) : } @JvmName("setWithEntityIdExpression") - operator fun , E : Expression> set(column: Column, value: E) { + operator fun > set(column: Column?>, value: Expression) { require(!values.containsKey(column)) { "$column is already initialized" } column.columnType.validateValueBeforeUpdate(value) values[column] = value } @JvmName("setWithEntityIdValue") - operator fun , ID : EntityID, E : S?> set(column: Column, value: E) { + operator fun > set(column: Column?>, value: S?) { require(!values.containsKey(column)) { "$column is already initialized" } column.columnType.validateValueBeforeUpdate(value) values[column] = value } - open operator fun > set(column: Column, value: E) = update(column, value) + open operator fun set(column: Column, value: Expression) = update(column, value) open operator fun set(column: CompositeColumn, value: S) { column.getRealColumnsWithValues(value).forEach { (realColumn, itsValue) -> set(realColumn as Column, itsValue) } } - open fun update(column: Column, value: Expression) { + open fun update(column: Column, value: Expression) { require(!values.containsKey(column)) { "$column is already initialized" } column.columnType.validateValueBeforeUpdate(value) values[column] = value } - open fun update(column: Column, value: SqlExpressionBuilder.() -> Expression) { + open fun update(column: Column, value: SqlExpressionBuilder.() -> Expression) { require(!values.containsKey(column)) { "$column is already initialized" } val expression = SqlExpressionBuilder.value() column.columnType.validateValueBeforeUpdate(expression) diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt index f10a7c24b1..6442e644f9 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt @@ -501,7 +501,17 @@ class DDLTests : DatabaseTestsBase() { assertEquals(2L, Table2.selectAll().count()) Table2.update { - it[table1] = null + } + + Table2.update { + // = null can't decide between "null as Int?" and "null as EntityID" + // it[table1] = null + it[table1] = null as Int? + } + Table2.update { + // = null can't decide between "null as Int?" and "null as EntityID?" + // it[table1] = null + it[table1] = null as EntityID? } Table1.deleteAll() diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/InsertTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/InsertTests.kt index 56ecb66b97..0ad32c240b 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/InsertTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/InsertTests.kt @@ -6,10 +6,14 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IdTable import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.TestDB import org.jetbrains.exposed.sql.tests.currentDialectTest -import org.jetbrains.exposed.sql.tests.shared.* +import org.jetbrains.exposed.sql.tests.shared.assertEqualLists +import org.jetbrains.exposed.sql.tests.shared.assertEquals +import org.jetbrains.exposed.sql.tests.shared.assertFailAndRollback +import org.jetbrains.exposed.sql.tests.shared.expectException import org.jetbrains.exposed.sql.vendors.MysqlDialect import org.junit.Test import java.math.BigDecimal @@ -230,33 +234,37 @@ class InsertTests : DatabaseTestsBase() { val string = varchar("stringCol", 20) } - fun expression(value: String) = stringLiteral(value).trim().substring(2, 4) + fun T.verifyInsert(expectedIntValue: Int?, insertClause: T.(UpdateBuilder) -> Unit) { + fun expression(value: String) = stringLiteral(value).trim().substring(2, 4) - fun verify(value: String) { - val row = tbl.select { tbl.string eq value }.single() - assertEquals(row[tbl.string], value) + deleteAll() + insert { + it[tbl.string] = expression(" _test_ ") + insertClause(it) + } + val rows = selectAll().adjustSlice { slice(tbl.string, tbl.nullableInt) } + .map { mapOf("string" to it[tbl.string], "nullableInt" to it[tbl.nullableInt]) } + assertEquals( + listOf(mapOf("string" to "test", "nullableInt" to expectedIntValue)).toString(), + rows.toString() + ) } withTables(tbl) { - tbl.insert { - it[string] = expression(" _exp1_ ") + tbl.verifyInsert(null) { } - verify("exp1") - - tbl.insert { - it[string] = expression(" _exp2_ ") + tbl.verifyInsert(5) { it[nullableInt] = 5 } - verify("exp2") - - tbl.insert { - it[string] = expression(" _exp3_ ") + tbl.verifyInsert(null) { it[nullableInt] = null } - verify("exp3") + tbl.verifyInsert(null) { + it[nullableInt] = LiteralOp(nullableInt.columnType, null) + } } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt index 60716bfa0f..24a7580aa6 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt @@ -208,6 +208,26 @@ class EntityTests : DatabaseTestsBase() { } } + @Test + fun testOptionalReference() { + withTables(EntityTestsData.XTable, EntityTestsData.YTable) { + val y = EntityTestsData.YEntity.new { } + EntityTestsData.XTable.insert { + it[y1] = y.id + } + EntityTestsData.XTable.insert { + // = null can't decide between "null as String?" and "null as EntityId?" + // it[EntityTestsData.XTable.y1] = null + it[y1] = null as String? + } + EntityTestsData.XTable.insert { + // = null can't decide between "null as String?" and "null as EntityID?" + // it[EntityTestsData.XTable.y1] = null + it[y1] = null as EntityID? + } + } + } + object Boards : IntIdTable(name = "board") { val name = varchar("name", 255).index(isUnique = true) }