How to update table query in Slick - scala

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.

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

How to create Anorm query to skip updating None values in DB (Scala)

I am using Anorm (2.5.1) in my Play+Scala application (2.5.x, 2.11.11). I keep facing the issue quite often where if the case class argument value is None, I don't want that parameter value to be inserted/updated in SQL DB. For example:
case class EditableUser(
user_pk: String,
country: Option[String],
country_phone_code: Option[Int],
phonenumber: Option[String],
emailid: Option[String],
format_all: Option[String]
)
....
val eUser: EditableUser = EditableUser("PK0001", None, None, None, Some("xyz#email.com"), Some("yes"))
...
SQL"""
update #$USR SET
COUNTRY=${eUser.country},
COUNTRY_PHONE_CODE=${eUser.country_phone_code},
PHONENUMBER=${eUser.phonenumber},
EMAILID=${emailid},
FORMAT_ALL=${format_all}
where (lower(USER_PK)=lower(${eUser.user_pk}))
""".execute()
Here when the value is None, Anorm will insert 'null' into corresponding column in SQL DB. Instead I want to write the query in such a way that Anorm skips updating those values which are None i.e. does not overwrite.
You should use boundStatements/preparedStatement and while setting values for the query don’t set the values for the columns which are none.
For example
SQL(
"""
select * from Country c
join CountryLanguage l on l.CountryCode = c.Code
where c.code = {countryCode};
"""
).on("countryCode" -> "FRA")
Or in your case:
import play.api.db.DB
import anorm._
val stat = DB.withConnection(implicit c =>
SQL("SELECT name, email FROM user WHERE id={id}").on("id" -> 42)
)
While writing you your query you check if the value you are going to put in on(x->something) is not None if it’s nice don’t put it hence you will not update the values which are none.
Without the ability (or library) to access the attribute names themselves, it would still be possible, if slightly clunky in some circles, to build the update statement dynamically depending on the values that are present in the case class:
case class Foo(name:String, age:Option[Int], heightCm:Option[Int])
...
def phrase(k:String,v:Option[Int]):String=if (v.isDefined) s", $k={$k}" else ""
def update(foo:Foo) : Either[String, Foo] = DB.withConnection { implicit c =>
def stmt(foo:Foo) = "update foo set "+
//-- non option fields
"name={name}" +
//-- option fields
phrase("age", foo.age) +
phrase("heightCm", foo.heightCm)
SQL(stmt(foo))
.on('name -> name, 'age -> age, 'heightCm -> heightCm)
.executeUpdate()
The symbols that are not present in the actual submitted SQL can still be specified in the on. Catering for other data types also needed.

In Anorm is it possible to apply multiple ColumnAliaser to the same query

The scenario is similar to the question at How to better parse the same table twice with Anorm? however the described solutions on that question can no longer be used.
On the scenario where a Message has 2 users I need to parse the from_user and to_user with SQL joins.
case class User(id: Long, name: String)
case class Message(id: Long, body: String, to: User, from: User)
def userParser(alias: String): RowParser[User] = {
get[Long](alias + "_id") ~ get[String](alias + "_name") map {
case id~name => User(id, name)
}
}
val parser: RowParser[Message] = {
userParser("from_user") ~
userParser("to_user") ~
get[Long]("messages.id") ~
get[String]("messages.name") map {
case from~to~id~body => Message(id, body, to, from)
}
}
// More alias here possible ?
val aliaser: ColumnAliaser = ColumnAliaser.withPattern((0 to 2).toSet, "from_user.")
SQL"""
SELECT from_user.* , to_user.*, message.* FROM MESSAGE
JOIN USER from_user on from_user.id = message_from_user_id
JOIN USER to_user on to_user.id = message.to_user
"""
.asTry(parser, aliaser)
If I'm right thinking you want to apply multiple ColumnAliaser with different aliasing policies to the same query, it's important to understand that ColumnAliaser is "just" a specific implementation of Function[(Int, ColumnName), Option[String]], so it can be defined/composed as any Function, and is simplified by the factory functions in its companion object.
import anorm.{ ColumnAliaser, ColumnName }
val aliaser = new ColumnAliaser {
def as1 = ColumnAliaser.withPattern((0 to 2).toSet, "from_user.")
def as2 = ColumnAliaser.withPattern((2 to 4).toSet, "to_user.")
def apply(column: (Int, ColumnName)): Option[String] =
as1(column).orElse(as2(column))
}

Scala, Slick: How to use insert using auto generated number and byte array

object FingerprintsModel extends FingerprintDAO {
// Fingerprint class definition
class FingerprintsTable(tag: Tag) extends Table[Fingerprint](tag, "fingerprints") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def customerId = column[String]("customer_id", O.NotNull)
def template_one = column[Array[Byte]]("template_one", O.NotNull)
def template_two = column[Array[Byte]]("template_two", O.NotNull)
def created = column[DateTime]("created", O.NotNull)
def updated = column[Option[DateTime]]("updated")
def * = (id, customerId, template_one, template_two) <> (Fingerprint.tupled, Fingerprint.unapply _)
def fingerprint = foreignKey("CUSTOMER", customerId, CustomersModel.customers)(_.id)
}
and this is my insert statement:
FingerprintsModel.fingerprints.map(fi => (fi.customerId, fi.template_one, fi.template_two, fi.created))
.insert((id, fingerprint.template_one, fingerprint.template_two, new DateTime()))
Summary
There are two main modifications you need:
You will want a TableQuery[FingerprintsTable] to call insert (or += or ++=on); and
To get back the IDs inserted you need to use the returning method in Slick.
Worked example
It's hard to tell from the code you posted exactly what you have in mind. It would be helpful next time to simplify your example first.
I've assumed your model is something like this:
case class Fingerprint(
id: Long,
customerId: String,
template_one: Array[Byte]
)
I've left out one of the byte arrays and the created and updated fields as they don't seem relevant to the question. In other words, I've simplified.
The FingerprintTable seems ok. I'm ignoring the foreign key as that doesn't seem relevant. Oh, the O.NotNull are now deprecated (in Slick 3 at least). You can leave them off because your columns are not Option values.
What we need is the table query, which I'd add inside FingerprintsModel:
lazy val fingerprints = TableQuery[FingerprintsTable]
lazy val fingerprintsWithID = fingerprints returning fingerprints.map(_.id)
You could use fingerprints to insert data. But you've asked for the IDs back, so you want to use fingerprintsWithID.
Putting it all together (again, using Slick 3 here):
object FingerprintExample extends App {
import FingerprintsModel._
val testData = Seq(
Fingerprint(0L, "Alice", Array(0x01, 0x02)),
Fingerprint(0L, "Bob", Array(0x03, 0x04))
)
// A program that will create the schema, and insert the data, returning the IDs
val program = for {
_ <- fingerprints.schema.create
ids <- fingerprintsWithID ++= testData
} yield ids
// Run the program using an in-memory database
val db = Database.forConfig("h2mem1")
val future = db.run(program)
val result = Await.result(future, 10 seconds)
println(s"The result is: $result")
}
Produces:
The result is: List(1, 2)

How to use SQL "LIKE" operator in SLICK

Maybe a silly question. But I have not found an answer so far. So how do you represent the SQL's "LIKE" operator in SLICK?
Exactly as you normally would!
val query = for {
coffee <- Coffees if coffee.name like "%expresso%"
} yield (coffee.name, coffee.price)
Will generate SQL like
SELECT name, price FROM coffees WHERE NAME like '%expresso%';
This is how I got it to work:
// argMap is map of type [Str, Str]
val query = for {
coffee <- coffees if (
argMap.map{ case (k,v) =>
metric.column[String](k) like s"%${v}%"
}.reduce(_ && _)
)
} yield(coffee.name)
And then you can run this using your db:
val res = db.run(query.result)
Of course res is a future here that you need to use await to get the actual result.
Suppose you have a table named, logs with 3 fields -
id
message
datetime
You want to perform LIKE operation. So it will be:
def data(data: ReqData): Future[Seq[Syslog]] = {
sysLogTable
.filter(_.datetime >= data.datetimeFrom)
.filter(_.datetime <= data.datetimeUntil)
.filter(_.message like s"%${data.phrase}%")
.result
}
Note: for sysLogTable
val sysLogTable: TableQuery[SyslogsTable] = TableQuery[SyslogsTable]
class SyslogsTable(tag: Tag) extends Table[Syslog](tag, "logs") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def message = column[String]("message")
def datetime = column[Timestamp]("date")
def * = (id.?, message, datetime) <> ((Syslog.apply _).tupled, Syslog.unapply)
}
Note: for Syslog case class
case class Syslog(
id: Option[Long],
message: String,
datetime: Timestamp
)