Skip to content

Commit

Permalink
fix: EXPOSED-257 Upsert incorrectly parameterizes non-literal WHERE a…
Browse files Browse the repository at this point in the history
…rguments (#1965)

* fix: EXPOSED-257 Upsert incorectly parameterizes non-literal WHERE arguments

Currently when using upsert() with a where argument, only literal values generate
valid SQL. Attempting to use a value with a param() wrapper or the plain native
type results in an exception that the parameter count is incorrect.

This occurs because the where arguments were not correctly added to the list of
statement arguments in the super class, so an override has now been included in
UpsertStatement directly.
  • Loading branch information
bog-walk authored Jan 12, 2024
1 parent 1833f79 commit 29203d6
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 2 deletions.
2 changes: 2 additions & 0 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2963,6 +2963,8 @@ public class org/jetbrains/exposed/sql/statements/UpdateStatement : org/jetbrain

public class org/jetbrains/exposed/sql/statements/UpsertStatement : org/jetbrains/exposed/sql/statements/InsertStatement {
public fun <init> (Lorg/jetbrains/exposed/sql/Table;[Lorg/jetbrains/exposed/sql/Column;Ljava/util/List;Lorg/jetbrains/exposed/sql/Op;)V
public synthetic fun arguments ()Ljava/lang/Iterable;
public fun arguments ()Ljava/util/List;
public final fun getKeys ()[Lorg/jetbrains/exposed/sql/Column;
public final fun getOnUpdate ()Ljava/util/List;
public final fun getWhere ()Lorg/jetbrains/exposed/sql/Op;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,14 @@ open class InsertStatement<Key : Any>(
}

override fun arguments(): List<Iterable<Pair<IColumnType, Any?>>> {
return arguments!!.map { args ->
return arguments?.map { args ->
val builder = QueryBuilder(true)
args.filter { (_, value) ->
value != DefaultValueMarker
}.forEach { (column, value) ->
builder.registerArgument(column, value)
}
builder.args
}
} ?: emptyList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@ open class UpsertStatement<Key : Any>(
}
return functionProvider.upsert(table, arguments!!.first(), onUpdate, where, transaction, keys = keys)
}

override fun arguments(): List<Iterable<Pair<IColumnType, Any?>>> {
return arguments?.map { args ->
val builder = QueryBuilder(true)
args.filter { (_, value) ->
value != DefaultValueMarker
}.forEach { (column, value) ->
builder.registerArgument(column, value)
}
where?.toQueryBuilder(builder)
builder.args
} ?: emptyList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import org.jetbrains.exposed.exceptions.UnsupportedByDialectException
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.concat
import org.jetbrains.exposed.sql.SqlExpressionBuilder.less
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
import org.jetbrains.exposed.sql.SqlExpressionBuilder.neq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
import org.jetbrains.exposed.sql.statements.BatchUpsertStatement
import org.jetbrains.exposed.sql.tests.*
Expand Down Expand Up @@ -383,6 +385,38 @@ class UpsertTests : DatabaseTestsBase() {
}
}

@Test
fun testUpsertWithWhereParameterized() {
val tester = object : IntIdTable("tester") {
val name = varchar("name", 64).uniqueIndex()
val age = integer("age")
}

withTables(excludeSettings = TestDB.mySqlRelatedDB + upsertViaMergeDB, tester) {
val id1 = tester.upsert {
it[name] = "Anya"
it[age] = 10
} get tester.id
tester.upsert {
it[name] = "Anna"
it[age] = 50
}

val nameStartsWithA = tester.name like "A%"
val nameEndsWithA = tester.name like stringLiteral("%a")
val nameIsNotAnna = tester.name neq stringParam("Anna")
val updatedAge = 20
tester.upsert(tester.name, where = { nameStartsWithA and nameEndsWithA and nameIsNotAnna }) {
it[name] = "Anya"
it[age] = updatedAge
}

assertEquals(2, tester.selectAll().count())
val updatedResult = tester.selectAll().where { tester.age eq updatedAge }.single()
assertEquals(id1, updatedResult[tester.id])
}
}

@Test
fun testUpsertWithSubQuery() {
val tester1 = object : IntIdTable("tester_1") {
Expand Down

0 comments on commit 29203d6

Please sign in to comment.