Zio Quill. Update with nested query error - scala

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.

Related

Quill way of doing INSERT INTO ... SELECT FROM

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))

Troublesome conversion of plain PostgreSQL query to Slick query

I have a plain PostgreSQL query I'm having trouble translating into slick query. I go stuck in syntax soup when using groupBy clause.
SELECT u.id AS investor_id,
u.account_type,
i.first_name,
issuer_user.display_name AS issuer_name,
p.legal_name AS product_name,
v.investment_date,
iaa.as_of AS CCO_approval_date,
v.starting_investment_amount,
v.maturity_date,
v.product_interest_rate,
v.product_term_length,
i.user_information_id,
v.id AS investment_id
FROM investors u
JOIN
( SELECT ipi.investor_id,
ipi.first_name,
ipi.user_information_id
FROM investor_personal_information ipi
JOIN
( SELECT investor_id,
MAX(id) AS Max_Id
FROM investor_personal_information
GROUP BY investor_id ) M ON ipi.investor_id = m.investor_id
AND ipi.id = m.Max_Id ) i ON u.id = i.investor_id
JOIN investments v ON u.id = v.investor_id
JOIN sub_products AS sp ON v.sub_product_id = sp.id
JOIN products AS p ON p.id = sp.product_id
JOIN company AS c ON c.id = p.company_id
JOIN issuers AS issuer ON issuer.id = c.issuer_id
JOIN users AS issuer_user ON issuer.owner = issuer_user.id
JOIN investment_admin_approvals AS iaa ON iaa.investment_id = v.id
ORDER BY i.first_name DESC;
I've started writing it
val query = {
val investorInfoQuery = (for {
i <- InvestorPersonalInformation
} yield (i)).groupBy {
_.investorId
}.map {
case (id, rest) => {
id -> rest.map(_.id).max
}
}
}
I know I've to create base queries into one big query and apply joins on them separately. Can anybody help guiding me or providing me some examples? Slick is hard.
Looks pretty simple to write. I am not going to help you write the whole query, I will just give you an example which you can follow to wrtie your query.
Lets say you had following structure and respective table queries defined as employees, emplayeePackages and employeeSalaryCredits
case class Employee(id: String, name: String)
case class EmployeePackage(id: String, employeeId: String, baseSalary: Double)
case class EmployeeSalaryCredit(id: String, employeeId: String, salaryCredited: Double, date: ZonedDateTime)
Now lets say you want all salary credits for all employees with employee's id, employee's name, base salary, actual credited salary and date of salary credit then your query will look like
val queryExample = employees
.join(employeePackages)
.on({ case (e, ep ) => e.id === ep.employeeId })
.join(employeeSalaryCredits)
.on({ case ((e, ep), esc) => e.id === esc.employeeId })
.map({ case ((e, ep), esc) =>
(e.id, e.name, ep.baseSalary, esc.salaryCredited, esc.date)
})

Selecting specific columns in Slick 3.x throws a type mismatch

In this Slick function I read from a User table and return a SessionUser object (SessionUser has fewer columns than User).
The problem is that this code does not compile, for each field in SessionUser it gives me the error type mismatch; found : slick.lifted.Rep[String] required: String. What's the meaning of this error and how to fix it?
def readByUserid (userid: String) : Option[SessionUser] = {
val db = Database.forConfig(Constant.dbBank)
try {
val users = TableQuery[UserDB]
val action = users.filter(_.userid === userid)
.map(u => SessionUser(u.userid, u.firstName, u.lastName)).result
val future = db.run(action)
val result = Await.result(future, Duration.Inf)
result
}
finally db.close
}
You're using map operation in a wrong place: mapping over a Query object is an equivalent of the SELECT statement in SQL. You should place your map operation right after the result invocation:
val action = users.filter(_.userid === userid).result.map(_.headOption.map(u => SessionUser(u.userid, u.firstName, u.lastName)))
Since we are using only three columns from users table, we could express that by using map operation on a Query object. This way the underlying SQL statement will select only the columns we need:
val action = users.filter(_.userid === userid).map(u => (u.userid, u.firstName, u.lastName)).result.map(_.headOption.map {
case (userid, firstName, lastName) => SessionUser(userid, firstName, lastName)
})

Slick 3.0.0 Select and Create or Update

I'am in a situation where in I have to do a select first, use the value to issue a create. It is some versioning that I'm trying to implement. Here is the table definition:
class Table1(tag: Tag) extends Table[(Int, String, Int)](tag, "TABLE1") {
def id = column[Int]("ID")
def name = column[String]("NAME")
def version = column[Int]("VERSION")
def indexCol = index("_a", (id, version))
val tbl1Elems = TableQuery[Table1]
}
So when a request comes to create or update an entry in Table1, I have to do the following:
1. Select for the given id, if exists, get the version
2. Increment the version
3. Create a new entry
All that should happen in a single transaction. Here is what I have got so far:
// this entry should be first checked if the id exists and if yes get //the complete set of columns by applying a filter that returns the max //version
val table1 = Table1(2, "some name", 1)
for {
tbl1: Table1 <- tbl1MaxVersionFilter(table1.id)
maxVersion: Column[Int] = tbl1.version
result <- tbl1Elems += table1.copy(version = maxVersion + 1) // can't use this!!!
} yield result
I will later wrap that entire block in one transaction. But I',m wondering how to complete that will create a new version? How can I get the value maxVersion out of the Column so that I can increment 1 to it and use it?
I would go with a static query, with something like this
import scala.slick.jdbc.{StaticQuery=>Q}
def insertWithVersion(id: Int,name:String) =
( Q.u + "insert into table1 select " +?id + "," +?name + ", (
select coalese(max(version),1) from table1 where id=" +?id +")" ).execute
If you want to write it using slick way then take a look at the following
val tableOne = TableQuery[Table1]
def updateWithVersion(newId:Int,name:String):Unit = {
val version = tableOne.filter( _.id === newId).map( _.version).max.run.getOrElse(1)
tableOne += (newId,name,version)
}
The idea is select the max version in the same query and if there is no version use 1 and insert it. Also, as the whole logic is issued in a single statement, no extra transaction management is needed.
P.S. There might be some error is sql and code.

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.