Quill way of doing INSERT INTO ... SELECT FROM - scala

I'm trying to translate simple INSERT INTO...SELECT FROM query into a quote in Quill. First of all, I am failing to find a built-in way to do this, so ended up trying out to use infix query
val rawQuery = quote { (secondTableValues: List[Int]) => {
infix"""
INSERT INTO my_table (second_table_id)
VALUES (
${secondTableValues.map(stid => (SELECT id FROM second_table where id = $stid)).mkString(",")}}
)
""".as[Insert[Any]]
}}
databaseClient.run(rawQuery(List(1,2,3)))
This however does not compile as Quill can't build Ast for the query.
What I ended up doing is have a raw query and not using quotes and run it with executeAction.
So two questions
How would you do INSERT INTO...SELECT FROM in a built-in way?
What is wrong with the infix version above?

import io.getquill._
val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
import ctx._
case class Table1(id: Int)
case class Table2(id: Int, name: String)
def contains(list: List[Int]) = {
//SELECT e.id,'hello' FROM Table1 e WHERE e.id IN (?)
val q = quote(
query[Table1].filter(e => liftQuery(list).contains(e.id)).map(e => Table2(e.id, "hello"))
)
//insert into table2 SELECT e.id, 'hello' FROM Table1 e WHERE e.id IN (?)
// `${..}` is expect ast , not string
quote(infix"insert into table2 ${q}".as[Insert[Any]])
}
// test
val list = List(1)
ctx.run(contains(list))

Related

Zio Quill. Update with nested query error

Tell me how can I write a query to update a field with a nested query using quill?
The query that should be the result:
UPDATE table1 SET field = 'foo' WHERE id IN (SELECT id FROM table2 WHERE field IN (value1, value2))
In quill it loks like:
val table1 = quote(querySchema[Table1]("table1"))
val table2 = quote(querySchema[Table2]("table2"))
val params = List("param1", "param2")
quote{
table1
.filter(t => table2.filter(t2 => liftQuery(params).contains(t2.field))
.map(_.id)
.contains(t.id)
)
.update(_.field -> lift("foo"))
}
The compiler tells me that I will get the right request, but I keep getting an error.

How to perform an update with optional columns using Scala quill?

I'm working on a legacy system that uses Scala Quill. I want to have a generic update function in my DAO and only update the columns that are passed. For example:
override def updateStatus(
personId: Int,
name: Option[String] = None,
address: Option[String] = None): Long = ctx.run(
query[Person]
.filter(_.id == lift(personId))
.update(
p => if (name.isDefined) p.name -> lift(name.get) else p.name -> p.name,
p => if (address.isDefined) p.address -> lift(address.get) else p.address = p.address
)
)
The example above, although compilable, incurs in the following runtime exception:
org.postgresql.util.PSQLException: ERROR: column "unused" of relation "person" does not exist
Position: 99
Any suggestion?
Quill just translates your code to sql, so every part of code should be wrapeed in lift.
You will see translated sql code when you compile your scala code.
the if (name.isDefined) p.name -> lift(name.get) else p.name -> p.name cant be translated to sql.
And your code has some typo. such as p.address = p.address
BTW, it's not recommend to use .get in Option value. When we forget check the value, None.get will throw NoSuchElementException.
Try this
def updateStatus(
personId: Int,
name: Option[String] = None,
address: Option[String] = None
) = ctx.run {
query[Person]
.filter(_.id == lift(personId))
.update(
p => p.name -> lift(name.getOrElse(p.name)),
p => p.address -> lift(address.getOrElse(p.address))
)
}
it will be translate to sql like following
UPDATE person SET name = ?, address = ? WHERE id = ?

How to update table query in Slick

How can I convert Query[MappedProjection[Example, (Option[String], Int, UUID, UUID)], Example, Seq] to Query[Examples, Example, Seq]?
Details
I am trying to drop a column from an existing table(Examples in this case) and move the data to another table (Examples2 in this case). I don't want to change all the existing code base, so I plan to join these two tables and map the results to Example.
import slick.lifted.Tag
import slick.driver.PostgresDriver.api._
import java.util.UUID
case class Example(
field1: Option[String] = None,
field2: Int,
someForeignId: UUID,
id: UUID,
)
object Example
class Examples(tag: Tag) extends Table[Example](tag, "entityNotes") {
def field1 = column[Option[String]]("field1")
def field2 = column[Int]("field2")
def someForeignId = column[UUID]("someForeignId")
def id = column[UUID]("id", O.PrimaryKey)
def someForeignKey = foreignKey(
"someForeignIdToExamples2",
someForeignId,
Examples2.query,
)(
_.id.?
)
def * =
(
field1.?,
field2,
someForeignId,
id,
) <> ((Example.apply _).tupled, Example.unapply)
}
object Examples{
val query = TableQuery[Examples]
}
Basically, all the functions in the codebase call Examples.query. If I update that query by joining two tables, the problem will be solved (of course with a performance shortcoming because of one extra join for each call).
To use the query with the existing code base, we need to keep the type the same. For example, we we can use filter as follows:
val query_ = TableQuery[Examples]
val query: Query[Examples, Example, Seq] = query_.filter(_.field2 > 5)
Everything will work without a problem since we keep the type of the query as it is supposed to be.
However, I cannot do that with a join if I want to use data from the second table.
val query_ = TableQuery[Examples]
val query = query
.join(Examples2.query_)
.on(_.someForeignId === _.id)
.map({
case (e, e2) =>
((
e2.value.?,
e1.field2,
e2.id
e.id,
) <> ((Example.apply _).tupled, Example.unapply))
})
This is where I got stuck. Its type is Query[MappedProjection[Example, (Option[String], Int, UUID, UUID)], Example, Seq].
Can anyone help? Btw, we don't have to use map. This is just what I got so far.

Doobie. Compose .update.withGeneratedKeys() and .update.run

Referencing to this question.
I want to insert some entity by some condition. It can either be inserted or not. If the condition is true the entity is inserted. I want to insert some other data in various tables. It looks like this:
val q = sql"insert into some_table (some_field) select 42 where ...(some condition)"
val inserts = List(
sql"insert ...",
sql"insert ...",
sql"insert ..."
)
for {
id <- q.update.withGeneratedKeys[Long]("id")
_ <- inserts.reduce(_ ++ _).update.run
} yield id
The problem is this does not compile because the first insert is a fs2.Stream but the second one is not.
I was trying to replace _ <- inserts.reduce... with _ = inserts.reduce. The app can compile but inserts in the second line does not occur.
UPD
My possible way to solve this problem:
...
for {
idOpt <- q.update.withGeneratedKeys[Long]("id").compile.last
_ <- idOpt.fold(0.pure[ConnectionIO])(_ => inserts.reduce(_ ++ _).update.run)
} yield idOpt
This works, but IMHO this is not pretty. Is there a better way to do it?
One way to perform your batch inserts - if you have similar data - is to use updateMany - see doc:
import doobie._
type PersonInfo = (String, Option[Short])
def insertMany(ps: List[PersonInfo]): ConnectionIO[Int] = {
val sql = "insert into person (name, age) values (?, ?)"
Update[PersonInfo](sql).updateMany(ps)
}
// Some rows to insert
val data = List[PersonInfo](
("Frank", Some(12)),
("Daddy", None))
Also, if you remove.compile.last, you can use the fact that if your resulting Stream q.update.withGeneratedKeys[Long]("id") is empty, you 'exit early' the for-comprehension.
So all in all, here is what you could do:
import fs2.Stream
val result =
// Now the for-comprehension operates on a Stream instead of an Option
for {
r <- q.update.withGeneratedKeys[Long]("id")
_ <- Stream.eval(insertMany(data)) // insertMany(data) is defined like in the snippet above
} yield r
result.compile.last

Recursive tree-like table query with Slick

My table data forms a tree structure where one row can reference a parent row in the same table.
What I am trying to achieve, using Slick, is to write a query that will return a row and all it's children. Also, I would like to do the same, but write a query that will return a child and all it's ancestors.
In other words:
findDown(1) should return
List(Group(1, 0, "1"), Group(3, 1, "3 (Child of 1)"))
findUp(5) should return
List(Group(5, 2, "5 (Child of 2)"), Group(2, 0, "2"))
Here is a fully functional worksheet (except for the missing solutions ;-).
package com.exp.worksheets
import scala.slick.driver.H2Driver.simple._
object ParentChildTreeLookup {
implicit val session = Database.forURL("jdbc:h2:mem:test1;", driver = "org.h2.Driver").createSession()
session.withTransaction {
Groups.ddl.create
}
Groups.insertAll(
Group(1, 0, "1"),
Group(2, 0, "2"),
Group(3, 1, "3 (Child of 1)"),
Group(4, 3, "4 (Child of 3)"),
Group(5, 2, "5 (Child of 2)"),
Group(6, 2, "6 (Child of 2)"))
case class Group(
id: Long = -1,
id_parent: Long = -1,
label: String = "")
object Groups extends Table[Group]("GROUPS") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def id_parent = column[Long]("ID_PARENT")
def label = column[String]("LABEL")
def * = id ~ id_parent ~ label <> (Group, Group.unapply _)
def autoInc = id_parent ~ label returning id into {
case ((_, _), id) => id
}
def findDown(groupId: Long)(implicit session: Session) = { ??? }
def findUp(groupId: Long)(implicit session: Session) = { ??? }
}
}
A really bad, and static attempt at findDown may be something like:
private def groupsById = for {
group_id <- Parameters[Long]
g <- Groups; if g.id === group_id
} yield g
private def childrenByParentId = for {
parent_id <- Parameters[Long]
g <- Groups; if g.id_parent === parent_id
} yield g
def findDown(groupId: Long)(implicit session: Session) = { groupsById(groupId).list union childrenByParentId(groupId).list }
But, I'm looking for a way for Slick to recursively search the same table using the id and id_parent link. Any other good ways to solve the problem is really welcome. Keep in mind though, that it would be best to minimise the number of database round-trips.
You could try calling SQL from slick. The SQL call to go up the hierarchy would look something like this (This is for SQL Server):
WITH org_name AS
(
SELECT DISTINCT
parent.id AS parent_id,
parentname.label as parent_label,
child.id AS child_id,
childname.ConceptName as child_label
FROM
Group parent RIGHT OUTER JOIN
Group child ON child.parent_id = parent.id
),
jn AS
(
SELECT
parent_id,
parent_label,
child_id,
child_label
FROM
org_name
WHERE
parent_id = 5
UNION ALL
SELECT
C.parent_id,
C.parent_label,
C.child_id,
C.child_label
FROM
jn AS p JOIN
org_name AS C ON C.child_id = p.parent_id
)
SELECT DISTINCT
jn.parent_id,
jn.parent_label,
jn.child_id,
jn.child_label
FROM
jn
ORDER BY
1;
If you want to go down the hierarchy change the line:
org_name AS C ON C.child_id = p.parent_id
to
org_name AS C ON C.parent_id = p.child_id
In plain SQL this would be tricky. You would have multiple options:
Use a stored procedure to collect the correct records (recursively). Then convert those records into a tree using code
Select all the records and convert those into a tree using code
Use more advanced technique as described here (from Optimized SQL for tree structures) and here. Then convert those records into a tree using code
Depending on the way you want to do it in SQL you need to build a Slick query. The concept of Leaky Abstractions is very evident here.
So getting the tree structure requires two steps:
Get the correct (or all records)
Build (using regular code) a tree from those records
Since you are using Slick I don't think it's an option, but another database type might be a better fit for your data model. Check out NoSQL for the differences between the different types.