eftshift0
eftshift0

Reputation: 30317

How to define an empty query

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]

Upvotes: 0

Views: 429

Answers (2)

Boris Azanov
Boris Azanov

Reputation: 4501

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)

Upvotes: 1

eftshift0
eftshift0

Reputation: 30317

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.

Upvotes: 0

Related Questions