Skip to content

Commit 85b978d

Browse files
committed
Fixing onConflict case with existing column used
1 parent 8e43a94 commit 85b978d

File tree

4 files changed

+37
-3
lines changed

4 files changed

+37
-3
lines changed

quill-engine/src/main/scala/io/getquill/sql/idiom/OnConflictSupport.scala

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,29 @@ trait OnConflictSupport {
2222

2323
val customAstTokenizer =
2424
Tokenizer.withFallback[Ast](self.astTokenizer(_, strategy, idiomContext)) {
25+
case Property(_: OnConflict.Excluded, value) => stmt"EXCLUDED.${value.token}"
26+
27+
// At first glance it might be hard to understand why this is doing `case OnConflict.Existing(a) => stmt""`
28+
// but consider that this is a situation where multiple aliases are used in multiple update clauses e.g. the `tt` in the below example
29+
// wouldn't even exist as a variable because in the query produced (also below) it would not even exist
30+
// i.e. since the table will be aliased as the first `existing table` variable i.e. `t`.
31+
// The second one `tt` wouldn't even exist.
32+
// ins.onConflictUpdate(_.i, _.s)(
33+
// (t, e) => t.l -> foo(t.l, e.l), (tt, ee) => tt.l -> bar(tt.l, ee.l)
34+
// )
35+
// This doesn't exist!!
36+
// v
37+
// > INSERT INTO TestEntity AS t (s,i,l,o,b) VALUES (?, ?, ?, ?, ?) ON CONFLICT (i,s) DO UPDATE SET l = foo(t.l, EXCLUDED.l), l = bar(tt.l, EXCLUDED.l)
38+
//
39+
// See the "cols target - update + infix" example for more detail
40+
case Property(_: OnConflict.Existing, value) => stmt"${value.token}"
41+
// As a backup if we have a standalone `OnConflict.Existing` or `OnConflict.Excluded` we just use
42+
// the `EXCLUDED` keyword directly or the empty string `` for the `Existing` case.
43+
// we can't have just the below two cases otherwise properties with the empty one `` would render as `.column`
44+
// which would be invalid SQL.
2545
case _: OnConflict.Excluded => stmt"EXCLUDED"
26-
case OnConflict.Existing(a) => stmt"${a.token}"
46+
case _: OnConflict.Existing => stmt""
47+
// Use the above action tokenizer for the `Action` case
2748
case a: Action =>
2849
self.actionTokenizer(customEntityTokenizer)(actionAstTokenizer, strategy, idiomContext).token(a)
2950
}

quill-sql-test/src/test/scala/io/getquill/context/sql/idiom/OnConflictSpec.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ trait OnConflictSpec extends Spec {
4040
def `cols target - update`(ins: Quoted[Insert[TestEntity]]) = quote {
4141
ins.onConflictUpdate(_.i, _.s)((t, e) => t.l -> (t.l + e.l) / 2, _.s -> _.s)
4242
}
43+
// In this situation the `tt` variable that the "existing" row is pointing to (in the second clause) wouldn't
44+
// even be created so clearly the variable name itself must be dropped and only the column reference should be used
45+
def `cols target - update + infix`(ins: Quoted[Insert[TestEntity]]) = quote {
46+
ins.onConflictUpdate(_.i, _.s)(
47+
(t, e) => t.l -> sql"foo(${t.l}, ${e.l})".as[Long],
48+
(tt, ee) => tt.l -> sql"bar(${tt.l}, ${ee.l})".as[Long]
49+
)
50+
}
4351
def insBatch = quote(liftQuery(List(e, TestEntity("s2", 1, 2L, Some(1), true))))
4452

4553
def `no target - ignore batch` = quote {

quill-sql-test/src/test/scala/io/getquill/context/sql/idiom/PostgresDialectSpec.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ class PostgresDialectSpec extends OnConflictSpec {
6060
}
6161
"cols target - update" in {
6262
ctx.run(`cols target - update`(i)).string mustEqual
63-
"INSERT INTO TestEntity AS t (s,i,l,o,b) VALUES (?, ?, ?, ?, ?) ON CONFLICT (i,s) DO UPDATE SET l = ((t.l + EXCLUDED.l) / 2), s = EXCLUDED.s"
63+
"INSERT INTO TestEntity AS t (s,i,l,o,b) VALUES (?, ?, ?, ?, ?) ON CONFLICT (i,s) DO UPDATE SET l = ((l + EXCLUDED.l) / 2), s = EXCLUDED.s"
64+
}
65+
"cols target - update + infix" in {
66+
println(ctx.run(`cols target - update + infix`(i)).string)
67+
ctx.run(`cols target - update + infix`(i)).string mustEqual
68+
"INSERT INTO TestEntity AS t (s,i,l,o,b) VALUES (?, ?, ?, ?, ?) ON CONFLICT (i,s) DO UPDATE SET l = foo(l, EXCLUDED.l), l = bar(l, EXCLUDED.l)"
6469
}
6570
}
6671
}

quill-sql-test/src/test/scala/io/getquill/context/sql/idiom/SqliteDialectSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class SqliteDialectSpec extends OnConflictSpec {
5151
}
5252
"cols target - update" in {
5353
ctx.run(`cols target - update`(i)).string mustEqual
54-
"INSERT INTO TestEntity AS t (s,i,l,o,b) VALUES (?, ?, ?, ?, ?) ON CONFLICT (i,s) DO UPDATE SET l = ((t.l + EXCLUDED.l) / 2), s = EXCLUDED.s"
54+
"INSERT INTO TestEntity AS t (s,i,l,o,b) VALUES (?, ?, ?, ?, ?) ON CONFLICT (i,s) DO UPDATE SET l = ((l + EXCLUDED.l) / 2), s = EXCLUDED.s"
5555
}
5656
}
5757
}

0 commit comments

Comments
 (0)