Inside a for-comp I need to set up some queries that will be then run in a single shot later... say, something like:
val queries = for {
_ <- query1
_ <- query2
id <- someInsertQuery
} yield id
db.run(queries.transactionally)
Tip: the queries are instances of slick.jdbc.JdbcActionComponent#ProfileAction
Now, I need to set up conditions on the first 2 queries. If a condition is met, the queries should run, nothing otherwise. So I thought:
val queries = for {
_ <- if (condition1) query1 else Query.empty
_ <- if (condition2) query2 else Query.empty
id <- someInsertQuery
} yield id
db.run(queries.transactionally)
But this doesn't work:
value flatMap is not a member of Object
[error] _ <- if (condition1) query1 else Query.empty
[error] ^
So I bet what I am trying to do is not this way. What would be the proper way to achieve this?
Update1:
More details. The first item in the for-comp looks like this (considering Boris' idea):
val queries = for {
_ <- someOption.map(someRow => SomeTableQuery += someRow).geOrElse(Query.empty.result)
}
SomeTableQuery is an instance of slick.lifted.TableQuery[SomeClass] and someOption is Option[SomeClass]
I think you have some misunderstanding with for-yield construction.
In the scala, for-yield is just syntactic sugar under some combinations of flatMap, map functions.
val queries = for {
_ <- if (condition1) query1 else Query.empty
_ <- if (condition2) query2 else Query.empty
res <- someInsertQuery
} yield res
for the compiler it is the same as:
query1.flatMap(_ => query2.flatMap(_ => someInsertQuery.map(res => res)))
if your query1 and query2 is some ProfileAction:
val query1: ProfileAction[Int, NoStream, Effect.Write] = ???
val query2: ProfileAction[Int, NoStream, Effect.Write] = ???
so, the type of the expression if (condition1) query1 else Query.empty is Object, because Query.empty has type - Query[Unit, Unit, Seq] and the nearest common ancestor of Query and ProfileAction is an Object type, which has not flatMap function. To make your code compilable, you should make all branches of if ... else construction having the same type which has flatMap function, here it would be so if we call result on Query.empty:
val result1: FixedSqlAction[Any, NoStream, Effect.Write with Effect.Read] = if (condition1) query1 else Query.empty.result
val result2: FixedSqlAction[Any, NoStream, Effect.Write with Effect.Read] = if (condition2) query2 else Query.empty.result
Possible version of your code:
import slick.jdbc.JdbcBackend
import slick.lifted.Query
import slick.jdbc.H2Profile.api._
val db: JdbcBackend.Database = ???
val query1: ProfileAction[Int, NoStream, Effect.Write] = ???
val query2: ProfileAction[Int, NoStream, Effect.Write] = ???
val someInsertQuery: ProfileAction[Int, NoStream, Effect.Write] = ???
val condition1 = false
val condition2 = true
val queries = for {
_ <- if (condition1) query1 else Query.empty.result
_ <- if (condition2) query2 else Query.empty.result
res <- someInsertQuery
} yield res
db.run(queries.transactionally)
If your query is actually options and not just queries, you can write option composition using flatMap for filtering empty options and use seq for sequential execution of result queries sequence. Example, using table in slick documentation:
import slick.jdbc.H2Profile.api._
case class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") {
def name = column[String]("COF_NAME")
def price = column[Double]("PRICE")
def * = (name, price)
}
val coffees: TableQuery[Coffees] = TableQuery[Coffees]
val maybeQuery1 = Option(("Colombian", 7.99))
val maybeQuery2 = Option(("Another name", 14.59))
val maybeQuery3 = Option(("Name", 4.39))
val queries = Seq(maybeQuery1, maybeQuery2, maybeQuery3).flatMap(_.map(someRow => coffees += someRow))
val db: Database = ???
db.run(DBIO.seq(queries:_*).transactionally)
The best way I have been able to solve it til now is by getting the condition inside a filter. Roughly:
val queries = for {
_ <- query1.filter(condition1)
_ <- query2.filter(condition2)
id <- someInsertQuery
} yield id
db.run(queries.transactionally)
Let me know if you have other ideas.
Related
I have a code which is using for-comprehension to run database query :
val totalFeeNoticeAmountFromDB = Future(/..Doing db job../)(executionContext)
val listOfRestrictedFundFromDB = Future(/..Doing db job../)(executionContext)
val res = for {
totalFeeNoticeAmount <- totalFeeNoticeAmountFromDB
listOfRestrictedFund <- listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
We know for running for-comprehension we need to pass implicit execution context.
But in this case I am wanting to pass execution context manually.
What is the way ?
Edited:
val res = for {
totalFeeNoticeAmount <-(?:ExecutionContext) totalFeeNoticeAmountFromDB
listOfRestrictedFund <-(?:ExecutionContext) listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
totalFeeNoticeAmountFromDB and listOfRestrictedFundFromDB are both Future type already initiated.
Is there any way of passing here
<-(?:ExecutionContext) ?
Perhaps consider scala-async which has gained experimental compiler support -Xasync in Scala 2.13.3 where the following for-comprehension
for {
a <- Future(41)
b <- Future(1)
} yield {
a + b
}
can be rewritten as
async {
val a = async(41)(ec)
val b = async(1)(ec)
await(a) + await(b)
}(ec)
where we can pass in execution context ec explicitly without resorting to flatMap/map.
Another hacky option could be better-monadic-for which supports defining implicits inside for-comprehensions
val ec: ExecutionContext = ???
(for {
implicit0(ec: ExecutionContext) <- Future.successful(ec)
a <- Future(41)(ec)
b <- Future(1)(ec)
} yield {
a + b
})(ec)
You can rewrite
val res = for {
totalFeeNoticeAmount <- totalFeeNoticeAmountFromDB
listOfRestrictedFund <- listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
as
val res = totalFeeNoticeAmountFromDB.flatMap(totalFeeNoticeAmount =>
listOfRestrictedFundFromDB.map(listOfRestrictedFund =>
(totalFeeNoticeAmount, listOfRestrictedFund)
)
)
For example if totalFeeNoticeAmountFromDB and listOfRestrictedFundFromDB are Futures then you can pass implicit scala.concurrent.ExecutionContext.Implicits.global explicitly
val res = totalFeeNoticeAmountFromDB.flatMap(totalFeeNoticeAmount =>
listOfRestrictedFundFromDB.map(listOfRestrictedFund =>
(totalFeeNoticeAmount, listOfRestrictedFund)
)(scala.concurrent.ExecutionContext.Implicits.global)
)(scala.concurrent.ExecutionContext.Implicits.global)
I believe the simplest solution to this problem is just to create an auxiliary function.
def foo(implicit ec: ExecutionContext): Future[(Int, Int)] = {
val totalFeeNoticeAmountFromDB = Future(/..Doing db job../)
val listOfRestrictedFundFromDB = Future(/..Doing db job../)
for {
totalFeeNoticeAmount <- totalFeeNoticeAmountFromDB
listOfRestrictedFund <- listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
}
That way when you need it you can just call it like this: foo(ec = myExplicitExecutionContext)
In Slick I can do this:
val joinQuery: Query[(Rep[String], Rep[String]), (String, String), Seq] = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name)
Then I can access name like this:
joinQuery.map(_._1)
But I am not confortable using _1 to access the name.
Is there any way I can name the yielded fields?
Thank you.
Well... you can define your case class for result types, and use them to Shape the query result.
case class YourQueryResult(coffeeName: String, supplierName: String)
val joinQuery = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name)
val joinQueryResultFuture = database.run(joinQuery).map(seq => {
seq.map({ case (coffeeName, supplierName) => YourQueryResult(coffeeName, supplierName) })
})
But, lets say you want to further modify the query itself, you can use pattern matching,
val joinQuery = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name)
val modificationQuery = joinQuery.map({
case (coffeeName, supplierName) => coffeeName
})
That probably didn't work yet at that time, but now you can do the following by using mapTo[] - hope this helps whoever stumbles across this question.
case class YourQueryResult(coffeeName: String, supplierName: String)
val joinQuery = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name).mapTo[YourQueryResult]
and then access as
joinQuery.map(_.coffeeName)
Using documentation in Slick 3.1.0 as source for classes :
implicit class PersonExtensions[C[_]](q: Query[People, Person, C]) {
def withAddress = q.join(addresses).on(_.addressId === _.id)
def withContact = q.join(contacts).on(_.contactId === _.id)
}
I would like to be able to do something like this :
val chrisQuery = people.filter(_.id === 2)
val chrisWithAddressWithContact: Future[((Person, Address), Contact)] =
db.run(chrisQuery.withAddress.withContact.result.head)
val chrisWithContactWithAddress: Future[((Person, Contact), Address)] =
db.run(chrisQuery.withContact.withAddress.result.head)
You can compose queries with zip.
val q1: Query[Xs, X, Seq]
val q2: Query[Ys, Y, Seq]
val query: Query[(Xs, Ys), (X, Y), Seq] = q1 zip q2
val results: DBIO[Seq[(X, Y)]] = query.result
val result: DBIO[(X, Y)] = results.head
db.run(result).map { case (r1: X, r2: Y) => ...}
Of course you can omit types and inline everything :)
Doc: http://slick.typesafe.com/doc/3.0.0/queries.html#zip-joins
I'm trying to get counts from a DB using a groupBy on my Scala+Slick code.
Here's my partial code :
object DBJobs extends Table[DBJob]("encoder_job") {
object Status extends Enumeration {
val local = Value("LOCAL")
val encoding = Value("ENCODING")
val done = Value("DONE")
val error = Value("ERROR")
}
implicit val StatusMapper = MappedTypeMapper.base[Status.Value, String] (
{x => x.toString},
{x => x match {case "LOCAL"=>Status(0);case "ENCODING"=>Status(1);case "DONE"=>Status(2);case "ERROR"=>Status(3)}}
)
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def status = column[DBJobs.Status.Value]("status", O.NotNull)
def getStats()(implicit session:Session):mutable.Map[Status.Value, Int] = {
var map = mutable.Map[Column[Status.Value], Column[Int]]()
val q = (for { j <- DBJobs } yield (j)).groupBy(_.status).map{
case (s, results) =>
map = map += (s -> results.length)
}
map
}
}
My problem is how to put data in my Map as [DBJobs.Status, Int] instead of [Column[Status.Value], Column[Int]].
Here's the SQL equivalent :
SELECT COUNT( 1 ), status FROM encoder_job GROUP BY STATUS
Slick version: 1.0.1
Thanks
def getStats()(implicit session:Session):mutable.Map[Status.Value, Int] = {
Query(DBJobs).groupBy(_.status).map{
case (s, results) => (s -> results.length)
}
}
// usage
val results = getStats.run
Be aware that putting methods in the table object directly cannot be easily migrated to Slick 2.0. Put them separate, e.g. as method extensions. Also see https://groups.google.com/d/msg/scalaquery/xNtPT6sexXI/zlkgxv6lZ6YJ
I'm trying to get the hang of working "the Scala way" so I was wondering if the following code is how things should be done in this case.
So I have the entities User and Company (mapped with LiftWeb mapper). User has currentUser which contains an Option[User] and Company has currentCompany which is an Option[Company]. In order to compare if the current user is the owner of the current company I'm doing something like:
Company.currentCompany.map{_.owner.get == User.currentUser.map{_.id.get}.openOr(-1) }.openOr(false)
It works but somehow it feels kinda verbose. Is it good? Is it not? Any better ideas?
Thanks!
Have you looked at using for-comprehensions? You could do something like the following:
for(
company <- Company.currentCompany.map{_.owner};
user <- User.currentUser.map{_.id}
) yield (company == user).getOrElse(false)
This will return true if Company.currentCompany is Some[value], and User.currentCompany is Some[value], and company.owner == user.id.
I feel there should be some way of getting rid of that getOrElse on the end, and returning the unwrapped boolean directly, hopefully someone else might be able to shed some light on this!
Using for-comprehension is definitively the solution, actually... or flatMap but less readable
To recall every generators are bound using flatMap function of the Monadic Option, except the last which is mapped (like any for and yield). Here is a good slideshow on the underneath concepts Monad
So the for comprehension is used to pass through all steps while they aren't encoded in the fail state (None for Option).
Here is a full example with four tests (the four basic cases) for each options (for and flatMap)
case class User(id: String) {
}
object User {
def currentUser(implicit me: Option[User]): Option[User] = me
}
case class Company(owner: Option[User]) {
}
object Company {
def currentCompany(implicit myCompany: Option[Company]): Option[Company] = myCompany
}
object Test extends App {
test1()
test2()
test3()
test4()
test5()
test6()
test7()
test8()
def test1() {
implicit val me: Option[User] = None
implicit val myCompany: Option[Company] = None
val v: Boolean = (for {
c <- Company.currentCompany
u <- User.currentUser
o <- c.owner if o.id == u.id
} yield true) getOrElse false
println(v)
}
def test2() {
implicit val me: Option[User] = Some(User("me"))
implicit val myCompany: Option[Company] = None
val v: Boolean = (for {
c <- Company.currentCompany
u <- User.currentUser
o <- c.owner if o.id == u.id
} yield true) getOrElse false
println(v)
}
def test3() {
implicit val me: Option[User] = None
implicit val myCompany = Some(Company(me))
val v: Boolean = (for {
c <- Company.currentCompany
u <- User.currentUser
o <- c.owner if o.id == u.id
} yield true) getOrElse false
println(v)
}
def test4() {
implicit val me: Option[User] = Some(User("me"))
implicit val myCompany = Some(Company(me))
val v: Boolean = (for {
c <- Company.currentCompany
u <- User.currentUser
o <- c.owner if o.id == u.id
} yield true) getOrElse false
println(v)
}
def test5() {
implicit val me: Option[User] = None
implicit val myCompany: Option[Company] = None
val v:Boolean = Company.currentCompany.flatMap(c => User.currentUser.flatMap( u => c.owner.map(o => if (u.id == o.id) true else false))) getOrElse false
println(v)
}
def test6() {
implicit val me: Option[User] = Some(User("me"))
implicit val myCompany: Option[Company] = None
val v:Boolean = Company.currentCompany.flatMap(c => User.currentUser.flatMap( u => c.owner.map(o => if (u.id == o.id) true else false))) getOrElse false
println(v)
}
def test7() {
implicit val me: Option[User] = None
implicit val myCompany = Some(Company(me))
val v:Boolean = Company.currentCompany.flatMap(c => User.currentUser.flatMap( u => c.owner.map(o => if (u.id == o.id) true else false))) getOrElse false
println(v)
}
def test8() {
implicit val me: Option[User] = Some(User("me"))
implicit val myCompany = Some(Company(me))
val v:Boolean = Company.currentCompany.flatMap(c => User.currentUser.flatMap( u => c.owner.map(o => if (u.id == o.id) true else false))) getOrElse false
println(v)
}
}
Given:
Case class user(name:String)
Case class company(owner:Option[User])
Val currentcompany=company(Some("Karl"))
Val currentuser=Some(user("Karl"))
Possible solution:
currentcompany.foldLeft(false) {
case (a,b) => currentuser.isDefined && b.owner == currentUser.get
}