diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt index a29b267723..2c0e59c1f8 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt @@ -539,7 +539,7 @@ abstract class FunctionProvider { val autoIncColumn = table.autoIncColumn val nextValExpression = autoIncColumn?.autoIncColumnType?.nextValExpression val dataColumnsWithoutAutoInc = autoIncColumn?.let { dataColumns - autoIncColumn } ?: dataColumns - val updateColumns = dataColumns.filter { it !in keyColumns } + val updateColumns = dataColumns.filter { it !in keyColumns }.ifEmpty { dataColumns } return with(QueryBuilder(true)) { +"MERGE INTO " 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 8946649cb6..63fefcb0fa 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 @@ -249,7 +249,8 @@ internal object PostgreSQLFunctionProvider : FunctionProvider() { transaction.throwUnsupportedException("UPSERT requires a unique key or constraint as a conflict target") } - val updateColumns = data.unzip().first.filter { it !in keyColumns } + val dataColumns = data.unzip().first + val updateColumns = dataColumns.filter { it !in keyColumns }.ifEmpty { dataColumns } return with(QueryBuilder(true)) { appendInsertToUpsertClause(table, data, transaction) 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 31410675b2..90d16627cb 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 @@ -216,7 +216,8 @@ internal object SQLiteFunctionProvider : FunctionProvider() { } +" DO" - val updateColumns = data.unzip().first.filter { it !in keyColumns } + val dataColumns = data.unzip().first + val updateColumns = dataColumns.filter { it !in keyColumns }.ifEmpty { dataColumns } appendUpdateToUpsertClause(table, updateColumns, onUpdate, transaction, isAliasNeeded = false) where?.let { diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/UpsertTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/UpsertTests.kt index 554f95a621..82d97c8841 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/UpsertTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/UpsertTests.kt @@ -1,6 +1,7 @@ package org.jetbrains.exposed.sql.tests.shared.dml import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.exceptions.UnsupportedByDialectException import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.concat @@ -14,6 +15,7 @@ import org.jetbrains.exposed.sql.tests.shared.expectException import org.junit.Test import java.util.* import kotlin.properties.Delegates +import kotlin.test.assertNotNull // Upsert implementation does not support H2 version 1 // https://youtrack.jetbrains.com/issue/EXPOSED-30/Phase-Out-Support-for-H2-Version-1.x @@ -86,6 +88,46 @@ class UpsertTests : DatabaseTestsBase() { } } + @Test + fun testUpsertWithAllColumnsInPK() { + val tester = object : Table("tester") { + val userId = varchar("user_id", 32) + val keyId = varchar("key_id", 32) + override val primaryKey = PrimaryKey(userId, keyId) + } + + fun upsertOnlyKeyColumns(values: Pair) { + tester.upsert { + it[userId] = values.first + it[keyId] = values.second + } + } + + withTables(tester) { testDb -> + excludingH2Version1(testDb) { + val primaryKeyValues = Pair("User A", "Key A") + if (testDb == TestDB.ORACLE) { + // Oracle explicitly prohibits using key columns in update clause + // throws 'ORA-38104: Columns referenced in the ON Clause cannot be updated' + expectException { + upsertOnlyKeyColumns(primaryKeyValues) + } + } else { + // insert new row + upsertOnlyKeyColumns(primaryKeyValues) + // 'update' existing row to have identical values + upsertOnlyKeyColumns(primaryKeyValues) + + val result = tester.selectAll().singleOrNull() + assertNotNull(result) + + val resultValues = Pair(result[tester.userId], result[tester.keyId]) + assertEquals(primaryKeyValues, resultValues) + } + } + } + } + @Test fun testUpsertWithUniqueIndexConflict() { withTables(Words) { testDb ->