Skip to content

Commit

Permalink
- Added support for onConflictIgnore to H2 (v1.4.200 in PostgreSQL mo…
Browse files Browse the repository at this point in the history
…de) (#1731)

- Doc updated with database actions return values
  • Loading branch information
b-gyula authored and juliano committed Dec 18, 2019
1 parent 25c7253 commit 39687c8
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 9 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,7 @@ Since you cannot use `Option[Table].isDefined`, if you want to null-check a whol
val q = quote {
query[Company].leftJoin(query[Address])
.on((c, a) => c.zip == a.zip) // Row type is (Company, Option[Address])
.filter({case(c,a) => a.isDefined}) // You cannot null-check a whole table
.filter({case(c,a) => a.isDefined}) // You cannot null-check a whole table!
}
```

Expand Down Expand Up @@ -1321,7 +1321,7 @@ Database actions are defined using quotations as well. These actions don't have
```scala
val a = quote(query[Contact].insert(lift(Contact(999, "+1510488988"))))

ctx.run(a)
ctx.run(a) // = 1 if the row was inserted 0 otherwise
// INSERT INTO Contact (personId,phone) VALUES (?, ?)
```

Expand All @@ -1339,10 +1339,10 @@ ctx.run(a)
### batch insert
```scala
val a = quote {
liftQuery(List(Person(0, "John", 31))).foreach(e => query[Person].insert(e))
liftQuery(List(Person(0, "John", 31),Person(2, "name2", 32))).foreach(e => query[Person].insert(e))
}

ctx.run(a)
ctx.run(a) //: List[Long] size = 2. Contains 1 @ positions, where row was inserted E.g List(1,1)
// INSERT INTO Person (id,name,age) VALUES (?, ?, ?)
```

Expand All @@ -1352,7 +1352,7 @@ val a = quote {
query[Person].filter(_.id == 999).update(lift(Person(999, "John", 22)))
}

ctx.run(a)
ctx.run(a) // = Long number of rows updated
// UPDATE Person SET id = ?, name = ?, age = ? WHERE id = 999
```

Expand Down Expand Up @@ -1382,12 +1382,12 @@ ctx.run(a)

```scala
val a = quote {
liftQuery(List(Person(1, "name", 31))).foreach { person =>
liftQuery(List(Person(1, "name", 31),Person(2, "name2", 32))).foreach { person =>
query[Person].filter(_.id == person.id).update(_.name -> person.name, _.age -> person.age)
}
}

ctx.run(a)
ctx.run(a) // : List[Long] size = 2. Contains 1 @ positions, where row was inserted E.g List(1,0)
// UPDATE Person SET name = ?, age = ? WHERE id = ?
```

Expand All @@ -1397,13 +1397,13 @@ val a = quote {
query[Person].filter(p => p.name == "").delete
}

ctx.run(a)
ctx.run(a) // = Long the number of rows deleted
// DELETE FROM Person WHERE name = ''
```

### insert or update (upsert, conflict)

Upsert is supported by Postgres, SQLite, and MySQL
Upsert is supported by Postgres, SQLite, MySQL and H2 `onConflictIgnore` only (since v1.4.200 in PostgreSQL compatibility mode)

#### Postgres and SQLite

Expand Down
19 changes: 19 additions & 0 deletions quill-sql/src/main/scala/io/getquill/H2Dialect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package io.getquill
import io.getquill.idiom.StatementInterpolator._
import java.util.concurrent.atomic.AtomicInteger

import io.getquill.ast.{ Ast, OnConflict }
import io.getquill.context.CanReturnField
import io.getquill.context.sql.idiom.PositionalBindVariables
import io.getquill.context.sql.idiom.SqlIdiom
import io.getquill.context.sql.idiom.ConcatSupport
import io.getquill.util.Messages.fail

trait H2Dialect
extends SqlIdiom
Expand All @@ -18,6 +20,23 @@ trait H2Dialect

override def prepareForProbing(string: String) =
s"PREPARE p${preparedStatementId.incrementAndGet.toString.token} AS $string}"

override def astTokenizer(implicit astTokenizer: Tokenizer[Ast], strategy: NamingStrategy): Tokenizer[Ast] =
Tokenizer[Ast] {
case c: OnConflict => c.token
case ast => super.astTokenizer.token(ast)
}

implicit def conflictTokenizer(implicit astTokenizer: Tokenizer[Ast], strategy: NamingStrategy): Tokenizer[OnConflict] = {
import OnConflict._
def tokenizer(implicit astTokenizer: Tokenizer[Ast]) =
Tokenizer[OnConflict] {
case OnConflict(i, NoTarget, Ignore) => stmt"${astTokenizer.token(i)} ON CONFLICT DO NOTHING"
case _ => fail("Only onConflictIgnore upsert is supported in H2 (v1.4.200+).")
}

tokenizer(super.astTokenizer)
}
}

object H2Dialect extends H2Dialect
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.getquill.context.sql.idiom

import io.getquill.{ H2Dialect, Literal, SqlMirrorContext, TestEntities }

class H2DialectSpec extends OnConflictSpec {
val ctx = new SqlMirrorContext(H2Dialect, Literal) with TestEntities
import ctx._
"OnConflict" - {
"no target - ignore" in {
ctx.run(`no target - ignore`).string mustEqual
"INSERT INTO TestEntity (s,i,l,o) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING"
}
"no target - ignore batch" in {
ctx.run(`no target - ignore batch`).groups.foreach {
_._1 mustEqual
"INSERT INTO TestEntity (s,i,l,o) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ trait OnConflictSpec extends Spec {
def `cols target - update` = quote {
ins.onConflictUpdate(_.i, _.s)((t, e) => t.l -> (t.l + e.l) / 2, _.s -> _.s)
}
def insBatch = quote(liftQuery(Seq(e, TestEntity("s2", 1, 2L, Some(1)))))

def `no target - ignore batch` = quote {
insBatch.foreach(query[TestEntity].insert(_).onConflictIgnore)
}
}

0 comments on commit 39687c8

Please sign in to comment.