Skip to content

Commit

Permalink
Add OnConflict to InsertValues (#61)
Browse files Browse the repository at this point in the history
Fixes #50 
Fixed `Returning.InsertBase[V[Expr]]` into
`Returning.InsertBase[V[Column]]`, looks like an oversight,
this basically enabled me to write an implicit in `OnConflictOps` that
made this fix possible
@lihaoyi I'm not sure how to organize tests in this case, I hope you
could have an idea, because current `OnConflictTests` are based on
functionality and they don't distinguish between internal implementation
of two different insert modes: `InsertValues` and `InsertColumns`, I
could have just duplicated the tests with `.insert.values(???)`, but it
doesn't seem right.
Another question is that there are kinda missing tests for
`InsertSelect` and then conflicts, do you want me to add those or was it
intentional to not have them?

---------

Co-authored-by: nikitaglushchenko <[email protected]>
  • Loading branch information
NPCRUS and npcrusde authored Jan 13, 2025
1 parent 87c730b commit 36df327
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 9 deletions.
203 changes: 200 additions & 3 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4556,6 +4556,37 @@ Buyer.insert
----
with `insert.values`
```scala
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictIgnore(_.id)
```
*
```sql
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING
```
*
```scala
0
```
### OnConflict.ignore.returningEmpty
Expand Down Expand Up @@ -4588,6 +4619,40 @@ Buyer.insert
----
with `insert.values`
```scala
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictIgnore(_.id)
.returning(_.name)
```
*
```sql
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?)
ON CONFLICT (id) DO NOTHING
RETURNING buyer.name AS res
```
*
```scala
Seq.empty[String]
```
### OnConflict.ignore.returningOne
Expand All @@ -4597,7 +4662,7 @@ Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 4 // This should cause a primary key conflict
_.id := 4
)
.onConflictIgnore(_.id)
.returning(_.name)
Expand All @@ -4620,6 +4685,40 @@ Buyer.insert
----
with `insert.values`
```scala
Buyer.insert
.values(
Buyer[Sc](
id = 5,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictIgnore(_.id)
.returning(_.name)
```
*
```sql
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?)
ON CONFLICT (id) DO NOTHING
RETURNING buyer.name AS res
```
*
```scala
Seq("test buyer")
```
### OnConflict.update
ScalaSql's `.onConflictUpdate` translates into SQL's `ON CONFLICT DO UPDATE`
Expand Down Expand Up @@ -4649,6 +4748,37 @@ Buyer.insert
----
with `insert.values`
```scala
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictUpdate(_.id)(_.dateOfBirth := LocalDate.parse("2023-10-10"))
```
*
```sql
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET date_of_birth = ?
```
*
```scala
1
```
----
Expand All @@ -4663,7 +4793,7 @@ Buyer.select
*
```scala
Seq(
Buyer[Sc](1, "TEST BUYER CONFLICT", LocalDate.parse("2001-02-03")),
Buyer[Sc](1, "TEST BUYER CONFLICT", LocalDate.parse("2023-10-10")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))
)
Expand Down Expand Up @@ -4700,6 +4830,37 @@ Buyer.insert
----
with `insert.values`
```scala
Buyer.insert
.values(
Buyer[Sc](
id = 3,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase)
```
*
```sql
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = UPPER(buyer.name)
```
*
```scala
1
```
----
Expand All @@ -4716,7 +4877,7 @@ Buyer.select
Seq(
Buyer[Sc](1, "JAMES BOND", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))
Buyer[Sc](3, "LI HAOYI", LocalDate.parse("1965-08-09"))
)
```
Expand Down Expand Up @@ -4756,6 +4917,42 @@ Buyer.insert
----
with `insert.values`
```scala
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictUpdate(_.id)(v => v.name := v.name.toLowerCase)
.returning(_.name)
.single
```
*
```sql
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?)
ON CONFLICT (id) DO UPDATE
SET name = LOWER(buyer.name)
RETURNING buyer.name AS res
```
*
```scala
"james bond"
```
## Values
Basic `VALUES` operations
### Values.basic
Expand Down
6 changes: 4 additions & 2 deletions scalasql/query/src/InsertValues.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package scalasql.query
import scalasql.core.{Context, DialectTypeMappers, Expr, Queryable, SqlStr, WithSqlExpr}
import scalasql.core.SqlStr.SqlStringSyntax

trait InsertValues[V[_[_]], R] extends Returning.InsertBase[V[Expr]] with Query.ExecuteUpdate[Int] {
trait InsertValues[V[_[_]], R]
extends Returning.InsertBase[V[Column]]
with Query.ExecuteUpdate[Int] {
def skipColumns(x: (V[Column] => Column[?])*): InsertValues[V, R]
}
object InsertValues {
Expand All @@ -16,7 +18,7 @@ object InsertValues {
) extends InsertValues[V, R] {

def table = insert.table
protected def expr = WithSqlExpr.get(insert).asInstanceOf[V[Expr]]
protected def expr: V[Column] = WithSqlExpr.get(insert)
override protected def queryConstruct(args: Queryable.ResultSetIterator): Int =
args.get(dialect.IntType)

Expand Down
7 changes: 6 additions & 1 deletion scalasql/src/dialects/OnConflictOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import scalasql.core.{Expr, WithSqlExpr}
import scalasql.query._

trait OnConflictOps {
implicit def OnConflictableInsertValues[V[_[_]], R](
implicit def OnConflictableInsertColumns[V[_[_]], R](
query: InsertColumns[V, R]
): OnConflict[V[Column], Int] =
new OnConflict[V[Column], Int](query, WithSqlExpr.get(query), query.table)

implicit def OnConflictableInsertValues[V[_[_]], R](
query: InsertValues[V, R]
): OnConflict[V[Column], Int] =
new OnConflict[V[Column], Int](query, WithSqlExpr.get(query), query.table)

implicit def OnConflictableInsertSelect[V[_[_]], C, R, R2](
query: InsertSelect[V, C, R, R2]
): OnConflict[V[Expr], Int] = {
Expand Down
Loading

0 comments on commit 36df327

Please sign in to comment.