Skip to content

Commit

Permalink
Fixing onConflict case with existing column used
Browse files Browse the repository at this point in the history
  • Loading branch information
deusaquilus committed Nov 24, 2024
1 parent 8e43a94 commit 85b978d
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,29 @@ trait OnConflictSupport {

val customAstTokenizer =
Tokenizer.withFallback[Ast](self.astTokenizer(_, strategy, idiomContext)) {
case Property(_: OnConflict.Excluded, value) => stmt"EXCLUDED.${value.token}"

// At first glance it might be hard to understand why this is doing `case OnConflict.Existing(a) => stmt""`
// but consider that this is a situation where multiple aliases are used in multiple update clauses e.g. the `tt` in the below example
// wouldn't even exist as a variable because in the query produced (also below) it would not even exist
// i.e. since the table will be aliased as the first `existing table` variable i.e. `t`.
// The second one `tt` wouldn't even exist.
// ins.onConflictUpdate(_.i, _.s)(
// (t, e) => t.l -> foo(t.l, e.l), (tt, ee) => tt.l -> bar(tt.l, ee.l)
// )
// This doesn't exist!!
// v
// > 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)
//
// See the "cols target - update + infix" example for more detail
case Property(_: OnConflict.Existing, value) => stmt"${value.token}"
// As a backup if we have a standalone `OnConflict.Existing` or `OnConflict.Excluded` we just use
// the `EXCLUDED` keyword directly or the empty string `` for the `Existing` case.
// we can't have just the below two cases otherwise properties with the empty one `` would render as `.column`
// which would be invalid SQL.
case _: OnConflict.Excluded => stmt"EXCLUDED"
case OnConflict.Existing(a) => stmt"${a.token}"
case _: OnConflict.Existing => stmt""
// Use the above action tokenizer for the `Action` case
case a: Action =>
self.actionTokenizer(customEntityTokenizer)(actionAstTokenizer, strategy, idiomContext).token(a)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ trait OnConflictSpec extends Spec {
def `cols target - update`(ins: Quoted[Insert[TestEntity]]) = quote {
ins.onConflictUpdate(_.i, _.s)((t, e) => t.l -> (t.l + e.l) / 2, _.s -> _.s)
}
// In this situation the `tt` variable that the "existing" row is pointing to (in the second clause) wouldn't
// even be created so clearly the variable name itself must be dropped and only the column reference should be used
def `cols target - update + infix`(ins: Quoted[Insert[TestEntity]]) = quote {
ins.onConflictUpdate(_.i, _.s)(
(t, e) => t.l -> sql"foo(${t.l}, ${e.l})".as[Long],
(tt, ee) => tt.l -> sql"bar(${tt.l}, ${ee.l})".as[Long]
)
}
def insBatch = quote(liftQuery(List(e, TestEntity("s2", 1, 2L, Some(1), true))))

def `no target - ignore batch` = quote {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ class PostgresDialectSpec extends OnConflictSpec {
}
"cols target - update" in {
ctx.run(`cols target - update`(i)).string mustEqual
"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"
"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"
}
"cols target - update + infix" in {
println(ctx.run(`cols target - update + infix`(i)).string)
ctx.run(`cols target - update + infix`(i)).string mustEqual
"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)"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class SqliteDialectSpec extends OnConflictSpec {
}
"cols target - update" in {
ctx.run(`cols target - update`(i)).string mustEqual
"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"
"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"
}
}
}

0 comments on commit 85b978d

Please sign in to comment.