I have a ZIO Quill project with a method that updates a bunch of fields in a database, and depending on a flag passed to the method, either clears another specific field or leaves it unchanged.
Here's some sample code that demonstrates the issue:
package com.example.dao
import com.zaxxer.hikari.HikariDataSource
import io.getquill.{PostgresJdbcContext, SnakeCase}
class MyDao(ds: HikariDataSource) {
lazy val ctx = new PostgresJdbcContext(SnakeCase, ds)
import ctx._
def updateValue(input: MyInput, shouldClear: Boolean): Unit =
if (shouldClear)
ctx.run(quote {
query[MyRecord]
.filter(_.id == lift(input.id))
.update(
_.value1 -> lift(input.value1),
_.value2 -> lift(input.value2),
_.value3 -> lift(input.value3),
_.value4 -> lift(input.value4),
_.value5 -> lift(input.value5),
_.keepOrClear -> None
)
})
else
ctx.run(quote {
query[MyRecord]
.filter(_.id == lift(input.id))
.update(
_.value1 -> lift(input.value1),
_.value2 -> lift(input.value2),
_.value3 -> lift(input.value3),
_.value4 -> lift(input.value4),
_.value5 -> lift(input.value5)
)
})
}
final case class MyRecord(
id: Int,
value1: String,
value2: String,
value3: String,
value4: String,
value5: String,
keepOrClear: Option[String]
)
final case class MyInput(id: Int, value1: String, value2: String,
value3: String, value4: String, value5: String)
This works just fine, generating the desired SQL:
UPDATE my_record AS x1
SET value1 = ?, value2 = ?, value3 = ?, value4 = ?, value5 = ?,
keep_or_clear = null
WHERE x1.id = ?;
UPDATE my_record AS x8
SET value1 = ?, value2 = ?, value3 = ?, value4 = ?, value5 = ?
WHERE x8.id = ?;
However, the two code paths of the if-else construct are near duplicates of each other. In my actual project, there's enough fields and tangential mapping going on that it's not obvious at a glance that the only thing that differs between the two if-else branches is the presence or absence of _.keepOrClear -> None
.
Is there some way to avoid this code duplication in a way that makes it obvious that only the _.keepOrClear -> None
is different, and is based on the keepOrClear
value? I'd potentially be willing to accept a less optimal SQL output, so long as it was still good enough for my needs, if it removed the duplication.
One thing I tried, which doesn't work and results in a compilation failure, is to set the value to None
if the flag is true, or setting it to the existing value if it's false.
def updateValue(input: MyInput, shouldClear: Boolean): Unit =
ctx.run(quote {
query[MyRecord]
.filter(_.id == lift(input.id))
.update(
_.value1 -> lift(input.value1),
_.value2 -> lift(input.value2),
_.value3 -> lift(input.value3),
_.value4 -> lift(input.value4),
_.value5 -> lift(input.value5),
r => r.keepOrClear -> (if (shouldClear) None else r.keepOrClear)
)
})
The error I get is:
/Users/Me/my-project/src/main/scala/com/example/dao/MyDao.scala:20:18: type mismatch;
found : Any
required: Option[String]
r => r.keepOrClear -> (if (shouldClear) None else r.keepOrClear)
I was hoping this might generate SQL like the following:
UPDATE my_record AS x1
SET value1 = ?, value2 = ?, value3 = ?, value4 = ?, value5 = ?,
keep_or_clear = CASE WHEN ? THEN null ELSE x1.keep_or_clear END
WHERE x1.id = ?;
The project is using Scala version 2.13.13 and Quill version 4.8.4.
You need to lift shouldClear
and coerce the conditional to a type.
Thus, doing:
def updateValue(input: MyInput, shouldClear: Boolean): Unit =
ctx.run(quote {
query[MyRecord]
.filter(_.id == lift(input.id))
.update(
_.value1 -> lift(input.value1),
_.value2 -> lift(input.value2),
_.value3 -> lift(input.value3),
_.value4 -> lift(input.value4),
_.value5 -> lift(input.value5),
r => r.keepOrClear -> ((if (lift(shouldClear)) None else r.keepOrClear): Option[String])
)
})
Results in:
UPDATE my_record AS x1 SET value1 = ?, value2 = ?, value3 = ?, value4 = ?, value5 = ?, keep_or_clear = CASE WHEN ? THEN null ELSE x1.keep_or_clear END WHERE x1.id = ?