I am starting to work with slick and scala and it seems that I still do not have the basics iron out:
I am using slick 3.0.0 and scala 2.11.7 and I am connecting to an Oracle database.
I want to get the number of rows in my table so I went and did a search and found the following:
1) This one tells me that .run does not exist:
mytable.length.run
2)This one tells me that there is a type mismatch found: slick.lifted.Rep[Int] and expected String:
var q = for(u <- mytable) yield u.id
print(q.size)
3)This one compiles and runs but prints Rep(Pure $#309962262):
var q = for{row <- mytable} yield row
println(Query(q.length))
So I am not sure if it is because I do not understand how this works but I was imagining that the following should happen:
A) constructQuery
b) "run" query.
So the other queries that I am using are as follow:
val db = Database.forConfig("Oracle")
try{
val f: Future[Unit] = {
val query: StreamingDBIO[Seq[String], String] = participants.map(_.id).result // A)"Construct query"
val stremQuery: DatabasePublisher[String] = db.stream(query) //B) "Run query"
streamQuery.foreach(println)
}
Await.result(f, Duration.Inf)
}
finally db.close
What am I missing? is number 3 not giving me what I want because is not under a db.stream/db.run/db.something command? Or am I just lost =)
Thanks in advance
Tona
Querying a database in slick basically consists of these three steps:
Create a Query
Convert Query to an Action
Execute the Action on a database
So your example would look something like this (types are optional and added for clarity):
val query = mytable.length // length is an aggregation
val action = query.result
val result: Future[Int] = db.run(action)
// Access result in a non blocking way (recommended):
result.map(count: Int => ...)
// or for completeness use Await (not recommended):
val count: Int = Await.result(result, Duration.Inf)
Further reading:
Slick documentation, Queries
Futures and Promises in scala
Related
Is it possible to update variable column list, which number is know only in runtime by slick 3.0?
Below is example what I want to do (won't compile)
var q: Query[UserTable, UserTable#TableElementType, Seq] = userTable
var columns = List[Any]()
var values = List[Any]()
if (updateCommands.name.isDefined) {
columns = q.name :: columns
values = updateCommands.name.get :: values
}
if (updateCommands.surname.isDefined) {
columns = q.surname :: columns
values = updateCommands.surname.get :: values
}
q = q.filter(_.id === updateCommands.id).map(columns).update(values)
Here is what I've done in Slick 3.1. I wasn't sure what worse, editing plain SQL statement or multiple queries. So I decided to go with latter assuming Postgres optimizer would see same WHERE clause in update queries of single transaction. My update method looks like this
def updateUser(user: User, obj: UserUpdate): Future[Unit] = {
val actions = mutable.ArrayBuffer[DBIOAction[Int, NoStream, Write with Transactional]]()
val query = users.withFilter(_.id === user.id)
obj.name.foreach(v => actions += query.map(_.name).update(v))
obj.email.foreach(v => actions += query.map(_.email).update(Option(v)))
obj.password.foreach(v => actions += query.map(_.pwdHash).update(Option(encryptPassword(v))))
slickDb.run(DBIO.seq(actions.map(_.transactionally): _*))
}
In Slick 3.0 they adopted slightly different approach, instead of having updateAll methods, as far as I userstand path of combinators was adopted.
So main idea is to define some actions on the data and then combine them ont he database to make a single run.
Example:
// let's assume that you have some table classes defined somewhere
// then let's define some actions, they might be really different
val action: SqlAction = YourTable.filter(_id === idToAssert)
val anotherAction = AnotherTable.filter(_.pets === "fun")
// and then we can combine them on a db.run
val combinedAction = for {
someResult <- action
anotherResult <- anotherAction
} yeild (someResult,anotherResult)
db.run(combinedAction) // that returns actual Future of the result type
In the same way you can deal with lists and sequences, for that please take a look here: http://slick.typesafe.com/doc/3.1.0-M1/dbio.html
DBIO has some functions that allows you to combine list of actions to one action.
I hope that idea is clear, if you have questions you are wellcome to the comments.
to update a variable number of columns you may use this way as I used for slick 3:
def update(id: Long, schedule: Schedule, fieldNames: Seq[String]): Future[_] = {
val columns = schedules.baseTableRow.create_*.map(_.name).toSeq.filter(fieldNames.map(_.toUpperCase).contains)
val toBeStored = schedule.withDefaults
val actions = mutable.ArrayBuffer[DBIOAction[Int, NoStream, Write with Transactional]]()
val query = schedules.withFilter(_.id === id)
//this is becasue of limitations in slick, multiple columns are not possible to be updated!
columns.find("NAME".equalsIgnoreCase).foreach(x => actions += query.map(_.name).update(toBeStored.name))
columns.find("NAMESPACE".equalsIgnoreCase).foreach(x => actions += query.map(_.namespace).update(toBeStored.namespace))
columns.find("URL".equalsIgnoreCase).foreach(x => actions +=
db.run(DBIO.seq(actions: _ *).transactionally.withPinnedSession)
}
If I want a returning value when inserting a new row, I can do something like
val insertQuery = myTable returning myTable.map(_.id) += SomeData(someString)
How can I achieve the same effect when deleting?
I tried
val deleteQuery = myTable filter (_.id ===id) returning myTable.map(_.someColumn) delete
But apparently this does not compile.
I can resort to the for comprehension but I wonder if there is a shorter way.
The best way I know of to do this is to do something like:
val query = db.filter(....)
val action = for {
results <- query.result
_ <- query.delete
} yield results
db.run(action.withTransactionIsolation(TransactionIsolation.RepeatableRead))
Wish it was shorter.
I'm confused by the way the slick 3 documentation describes transactions. I have slick 2 code that looks like this:
def doSomething(???) = DB.withTransaction { implicit session =>
userDao.doSomething(???)
addressDao.doSomething(???)
contactDao.doSomething(???)
}
How can i span a transaction in slick 3?
Please have a look at the documentation here http://slick.typesafe.com/doc/3.0.0/dbio.html#transactions-and-pinned-sessions
The idea is that you wrap a sequence of IO operations into a transactionally like shown in this example:
val a = (for {
ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
_ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally
val f: Future[Unit] = db.run(a)
This way Slick still process all the operations reactively, but it runs them all in one transaction sequentially.
So your example would look like this:
def doSomething(???) = (for {
_ <- userDao.doSomething(???)
_ <- addressDao.doSomething(???)
_ <- contactDao.doSomething(???)
} yield()).transactionally
val dbAction = (
for {
user <- userTable.doSomething
address <- addressTable.doSomething
contact <- contactTable.doSomething
} yield()
).transactionally
val resultFuture = db run dbAction
You just need to wrap your action into 'transactionally'. Slick would take care of running all wrapped DB actions as a transaction.
Apart from the standard advantages of having a more reactive/functional/async way of writing code, it allows couple of performance improvements. Like it can determine at runtime if multiple actions can use the same session or not. In Slick 2.0, whenever you use 'withTransaction' or 'withSession' that opens a new jdbc session while here it has potential to reuse the same.
In previous versions of Slick, there was insert function defined on a TableQuery that returned an insertion result. I'm migrating to the new API and doing something like:
DBIO.seq(someTable += someValue)
but this has type
dbio.DBIOAction[Unit, NoStream, Write]
How can I get back the rows affected?
edit
The problem here seems to be that the types aren't lining up the way I would expect them to. Here's what Scala sees the types as:
val q: PostgresDriver.DriverAction[PostgresDriver.InsertActionExtensionMethods[(String, String)]#SingleInsertResult, NoStream, Write] = Admins.tableQuery += ((username, grantor))
val seq: dbio.DBIOAction[Unit, NoStream, Write] = DBIO.seq(q)
db.run(seq)
This doesn't make sense to me. If q has type DriverAction[PostgresDriver.InsertActionExtensionMethods[(String, String)]#SingleInsertResult... where SingleInsertResult is a type alias to Int then shouldn't my DBIO.seq return a DBIOAction[Int, NoStream, Write]?
Look at these examples
It's not very different from slick 2.x, but now everything returns Futures.
Maybe something along the lines of (off the top of my head, I can't test it myself now)
val users = TableQuer[Users]
// this is the query
val q = (users += User(None, "Stefan", "Zeiger"))
// now you run it
db.run(q).map{ affectedRows =>
case 1 => println("success")
case _ => println("oops")
}
How do you 'un-lift' a value inside a query in Slick when using lifted embedding? I was hoping a 'get', 'toLong' or something like that may do the trick, but no such luck.
The following code does not compile:
val userById = for {
uid <- Parameters[Long]
u <- Users if u.id === uid
} yield u
val userFirstNameById = for {
uid <- Parameters[Long]
u <- userById(uid)
---------------^
// type mismatch; found : scala.slick.lifted.Column[Long] required: Long
} yield u.name
You can't, for 2 reasons:
1) with val this is happening at compile time, there is no Long
value uid. userById(uid) binds a Long uid to the compile time
generated prepared statement, and then .list, .first, etc. invoke
the query.
2) the other issue is as soon as you Parameterize a query,
composition is no longer possible -- it's a limitation dating back to
ScalaQuery.
Your best bet is to delay Parameterization until the final composed query:
val forFooBars = for{
f <- Foos
b <- Bars if f.id is b.fooID
} yield(f,b)
val allByStatus = for{ id ~ active <- Parameters[(Long,Boolean)]
(f,b) <- forFooBars if (f.id is id) && (b.active is active)
} yield(f,b)
def findAllByActive(id: Long, isActive: Boolean) = allByStatus(id, isActive).list
At any rate, in your example you could just as well do:
val byID = Users.createFinderBy(_.id)
The only way that I know to get this kind of thing to work is wrap the query val in a def and pass in a runtime variable, which means Slick has to re-generate the sql on every request, and no prepared statement is sent to underlying DBMS. In some cases you have to do this, like passing in a List(1,2,3) for inList.
def whenNothingElseWorks(id: Long) = {
val userFirstNameById = for {u <- userById(id.bind)} yield u.name
}