Slick: update List in db - postgresql

My table schema in Postgres is the following:
I store List[String] in the 2nd column and I wrote the working method that updates this list with Union of a new list and old list:
def update(userId: Long, unknownWords: List[String]) = db.run {
for {
y <- lists.filter(_.userId === userId).result
words = y.map(_.unknownWords).flatMap(_.union(unknownWords)).distinct.toList
x <- lists.filter(_.userId === userId).map(_.unknownWords).update(words)
} yield x
}
Is there any way to write this better? And maybe the question is pretty dumb, but I don’t quite understand why I should apply .result() to the first line of the for expression, the filter().map() chain on the 3d line is working fine, is there something wrong with the types?

Why .result
The reason you need to apply .result is to do with the difference between queries (Query type) and actions (DBIO) in Slick.
By itself, the lists.filter line is a query. However, the third line (the update) is an action. If you left the .result off your for comprehension would have a type mismatch between a Query and a DBIO (action).
Because you're going to db.run the result of the for comprehension, the for comprehension needs to result in an DBIO action, rather than a query. In other words, putting a .result there is the right thing to do because you're constructing an action to run in the database (namely, fetching some data for the user).
You'll then going to run another action later to update the database. So in all, you're using for to combine two actions (two runnable SQL expressions) into a single DBIO. That's the x you yield, which is executed by db.run.
Better?
This is working for you, and that's just fine.
There's a small amount of duplication. You might spot your query on the first line, is very similar to the update query. You could abstract that out into a value:
val userLists = lists.filter(_.userId === userId)
That's a query. In fact, you could go a step further and modify the query to just select the unknownWords column:
val userUnknownWords = lists.filter(_.userId === userId).map(_.unknownWords)
I've not tried to compile this but that would make your code something like:
def update(userId: Long, unknownWords: List[String]) = {
val userUnknownWords = lists.filter(_.userId === userId).map(_.unknownWords)
db.run {
for {
y <- userUnknowlWords.result
words = y.flatMap(_.union(unknownWords)).distinct.toList
x <- userUnknownWords.update(words)
} yield x
}
Given that you're composing two actions (a select and an update), you could use DBIO.flatMap in place of the for comprehension. You might find it clearer. Or not. But here's an example...
The argument to DBIO.flatMap needs to be another action. That is, flatMap is a way to sequence actions. In particular, it's a way to do that while using the value from the database.
So you could replace the for comprehension with:
val action: DBIO[Int] =
userUnknowlWords.result.flatMap { currentWords =>
userUnknownWords.update(
currentWords.flatMap(_.union(unknownWords)).distinct.toList
)
}
(Again, apologies for not compiling the above: I don't have the details of the types, but hopefully this will give a flavour for how the code could work).
The final action is the one you can pass to db.run. It returns the number of rows changed.

Related

Monadic way to get first Right to result from getting an Either from items of a list?

Up front: I know how to just write a custom function that will do this, but I swear there's a built-in thing whose name I'm just forgetting, to handle it in an idiomatic way. (Also, in my actual use case I'm likely to be using more complex monads involving state and assorted nonsense, and I feel like the answer I'm looking for will handle those as well, while the hand-coded one would need to be updated.)
I have a list items : List[A] and a function f : (A) -> Either[Error, B]. I vaguely recall there's an easy dedicated function that will apply f to each item in items and then return the first Right(b) to result, without applying f to the remaining items (or return Left[error] of the last error, if nothing succeeds.)
For example, if you had f(items(0)) result in Left("random error"), f(items(1)) result in Right("Find this one!"), and f(items(2)) result in launchTheNukes(); Right("Uh oh."), then the return should be Right("Find this one!") and no nukes should be launched.
It's sort of like what's happening in a for comprehension, where you could do:
for{
res0 <- f(items(0))
res1 <- f(items(1))
res2 <- f(items(2))
} yield res2
Which would return either the first error or the final result - so I want that, but to handle an arbitrary list rather than hard-coded, and returning the first success, not the first error. (The answer I'm looking for might be two functions, one to swap the sides of an Either, and one to automatically chain foldLefts across a list... I think there's a single-step solution though.)
Code snippet for commented solution:
def tester(i : Int) : Either[String, Int] = {if (i % 2 == 0) Right(100 / (4 - i)) else Left(i.toString)}
(1 to 5).collectFirst(tester)
I'm assuming (from your mention of more complex monads such as State) that you're using the Cats library.
You probably want one of the methods that come from Traverse
For example, its sequence and traverse methods are two variations of the "I have a list of things, and a thing-to-monad function, and I want a monad of things". Since Either is a monad whose flatMap aborts early upon encountering a Left, you could .swap your Eithers so that the flatMap aborts early upon encountering a Right, and then .swap the result back at the end.
def tester(i : Int): Either[String, Int] = /* from your question */
val items = (1 to 5).toList
items.traverse(tester(_).swap).swap // Right(50)
val allLeft = List(Left("oh no"), Left("uh oh"))
allLeft.traverse(_.swap).swap // Left(List("oh no", "uh oh"))
Ho about list.iterator.map(f).collectFirst { case Right(x) =>x } (this returns Option(x) of the first Right(x) it finds ... Could return Option(Right(x)) but that seems redundant.
Or you might go back to either:
list.iterator.map(f).collectFirst { case x#Right(_) => x }.getOrElse(Left("oops"))
If you insist on getting the last Left in case there are no Rights (doesn't seem to be very meaningful actually), then it seems like a simple recursive scan (that you said you knew how to write) is the best option.

Can I select a single row in Slick that is also guaranteed to be the only row returned?

In Slick, there are first and firstOption to select the first row of your result set.
Is there a version that also guarantees that this was the only row being returned (so that I get an exception if the dataset somehow breaks certain assumptions my application depends on)?
I don't know of anything built in to do that. What you can do is write a method that will take an action, check the results is exactly one row, and fail if it isn't.
That could just be a plain old method you wrap around an action. Or, we could enrich an action so it becomes a call you make on an action. Here's an example of the kind of thing I mean:
implicit class ResultEnrichment[T](action: DBIO[Seq[T]]) {
def exactlyOne: DBIO[T] = action.flatMap { xs =>
xs.length match {
case 1 => DBIO.successful(xs.head)
case n => DBIO.failed(new RuntimeException(s"Expected 1 result, not $n"))
}
}
}
This class is marked as implicit so the compiler can make use of it whenever there's a DBIO[Seq[T]]. Specifically, it supplies an exactlyOne method which the compiler can find on any action of Seq[T].
The result of exactlyOne is a new action you can run. It will fail if it doesn't find exactly one row at runtime.
You'd use it like this for some arbitrary query:
val query = table.filter(...).map(...) // whatever your query is
val action = query.result.exactlyOne
Run that final action and you'll get one result or an error at runtime.

Scala Slick - Convert TableQuery[E] to Query[E] to chain operations

I'm trying to apply a series of optional filtering operations to a query by using a list of the operations and folding over the list.
val table = TableQuery[Fizz]
val filters = List(filter1(option1)_, filter2(option2)_, filter3(option3)_)
val filteredQuery = filters.foldLeft(table){(q, filter) => filter(q)}
The partially applied filter functions have a signature of
Query[Fizz, FizzRow, Seq] => Query[Fizz, FizzRow, Seq]
Basically, in each function, I am optionally applying the filtering if the filter parameter option* is present. However, the compiler does not like the fact that I am passing in a TableQuery to a function that takes Query, even though TableQuery is a subtype of Query. Is there a way to convert a TableQuery to Query? Or a better way to go about chaining filter functions on a query?
The compiler error in question is
type mismatch;
found :scala.slick.lifted.Query[generated.Tables.Farm,generated.Tables.FarmRow,Seq]
required: scala.slick.lifted.TableQuery[generated.Tables.Farm]
I can get it to compile by using table.drop(0) instead of table but obviously that seems like a poor workaround. I see that there's a to method on TableQuery that converts it to a Query but it also takes an implicit ctc: TypedCollectionTypeConstructor[D].
An example of one of the filterX functions listed above:
def filterCharacteristics(characteristics: Option[List[Int]])(table: Query[Farm,FarmRow,Seq]) = {
characteristics.map(chars =>
(for {
(fc, f) <- Farmcharacteristic.filter(_.characteristicId inSet chars) join table on (_.farmId === _.farmId)
} yield f)).getOrElse(table)
}
I think you can try another approach. Instead of using a fold, you can use a collect to get only the Some values.
Then you can apply a filter to each of the options you have:
val table = TableQuery[Fizz]
val filteredQueries = List(Some(option1), Some(option2), Some(option3)) collect {
case Some(option) => option
} map { currentOption =>
table.filter(currentOption)
}
// We need to get the last value or the TableQuery
val lastValue = filteredQueries reverse headOption
// Or we have Some(Query) or None, In case it is a None, we will use table
lastValue.getOrElse(table)

Sharing database session between multiple methods in Slick 3

I have recently switched from Slick-2 to Slick-3. Everything is working very well with slick-3. However, I am having some issues when it comes to transaction.
I have seen different questions and sample code in which transactionally and withPinnedSession are used to handle the transaction. But my case is slightly different. Both transcationally and withPinnedSession can be applied on Query. But what I want to do is to pass the same session to another method which will do some operations and want to wrap multiple methods in same transaction.
I have the below slick-2 code, I am not sure how this can be implemented with Slick-3.
def insertWithTransaction(row: TTable#TableElementType)(implicit session: Session) = {
val entity = (query returning query.map(obj => obj) += row).asInstanceOf[TEntity]
// do some operations after insert
//eg: invoke another method for sending the notification
entity
}
override def insert(row: TTable#TableElementType) = {
db.withSession {
implicit session => {
insertWithTransaction(row)
}
}
}
Now, if someone is not interested in having transactions, they can just invoke the insert() method.
If we need to do some transaction, it can be done by using insertWithTransaction() in db.withTransaction block.
For eg :
db.withTransaction { implicit session =>
insertWithTransaction(row1)
insertWithTransaction(row2)
//check some condition, invoke session.rollback if something goes wrong
}
But with slick-3, the transactionally can be applied on query only.
That means, wherever we need to do some logic centrally after insertion, it is possible. Every developer needs to manually handle those scenarios explicitly, if they are using transactions. I believe this could potentially cause errors. I am trying to abstract the whole logic in insert operation so that the implementors need to worry only about the transaction success/failure
Is there any other way, in slick-3, in which I can pass the same session to multiple methods so that everything can be done in single db session.
You are missing something : .transactionally doesn't apply to a Query, but to a DBIOAction.
Then, a DBIOAction can be composed of multiple queries by using monadic composition.
Here is a exemple coming from the documentation :
val action = (for {
ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
_ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally
action is composed of a select query and as many delete queries as rows returned by the first query. All that creates DBIOAction that be executed in a transaction.
Then, to run the action against the database, you have to call db.run, so, like this:
val f: Future[Unit] = db.run(action)
Now, to come back to your exemple, let's say you want to apply an update query after your insert, you can create an action this way
val action = (for {
entity <- (query returning query.map(obj => obj) += row)
_ <- query.map(_.foo).update(newFoo)
} yield entity).transactionally
Hope it helps.

Scala's for-comprehension `if` statements

Is it possible in scala to specialize on the conditions inside an if within a for comprehension? I'm thinking along the lines of:
val collection: SomeGenericCollection[Int] = ...
trait CollectionFilter
case object Even extends CollectionFilter
case object Odd extends CollectionFilter
val evenColl = for { i <- collection if(Even) } yield i
//evenColl would be a SomeGenericEvenCollection instance
val oddColl = for { i <- collection if(Odd) } yield i
//oddColl would be a SomeGenericOddCollection instance
The gist is that by yielding i, I get a new collection of a potentially different type (hence me referring to it as "specialization")- as opposed to just a filtered-down version of the same GenericCollection type.
The reason I ask is that I saw something that I couldn't figure out (an example can be found on line 33 of this ScalaQuery example. What it does is create a query for a database (i.e. SELECT ... FROM ... WHERE ...), where I would have expected it to iterate over the results of said query.
So, I think you are asking if it is possible for the if statement in a for-comprehension to change the result type. The answer is "yes, but...".
First, understand how for-comprehensions are expanded. There are questions here on Stack Overflow discussing it, and there are parameters you can pass to the compiler so it will show you what's going on.
Anyway, this code:
val evenColl = for { i <- collection if(Even) } yield i
Is translated as:
val evenColl = collection.withFilter(i => Even).map(i => i)
So, if the withFilter method changes the collection type, it will do what you want -- in this simple case. On more complex cases, that alone won't work:
for {
x <- xs
y <- ys
if cond
} yield (x, y)
is translated as
xs.flatMap(ys.withFilter(y => cond).map(y => (x, y)))
In which case flatMap is deciding what type will be returned. If it takes the cue from what result was returned, then it can work.
Now, on Scala Collections, withFilter doesn't change the type of the collection. You could write your own classes that would do that, however.
yes you can - please refer to this tutorial for an easy example. The scala query example you cited is also iterating on the collection, it is then using that data to build the query.