I'm trying to do eager fetch with Anorm, this is lazy fetch like here https://gist.github.com/guillaumebort/2788715
package models
import java.util.Date
import anorm._
import anorm.SqlParser._
import play.api.db.DB
import play.api.Play.current
// table users
case class User(id: Option[Long] = None, firstName: Option[String], lastName: Option[String]) {
// mismatch types Set[Nicknames] and List[Nicknames] because * returns List
lazy val nickNames: Set[Nicknames] = DB.withConnection { implicit connection =>
SQL"""
SELECT * FROM nicknames
JOIN types t ON nicknames.type_id = t.id
JOIN events e ON nicknames.events_id = e.id
WHERE user.id = $id
""".as(Nickname.withTypeAndEvents.*)
}
}
object User {
val simple = {
get[Option[Long]]("users.id") ~
get[Option[String]]("users.first_name") ~
get[Option[String]]("users.last_name") map {
case id ~ firstName ~ lastName => User(id, firstName, lastName)
}
}
def findById(id: Long): Option[User] = DB.withConnection { implicit connection =>
SQL"SELECT * FROM users WHERE id = $id".as(simple.singleOpt)
}
}
// table nicknames
case class Nickname(id: Option[Long] = None, name: Option[String], startDate: Option[Date],
nType: Option[NickType] = None,
user: Option[User] = None,
events: Option[Set[Event]] = None) // I use Set here to remove duplicates
object Nickname {
val simple = {
get[Option[Long]]("nicknames.id") ~
get[Option[String]]("nicknames.name") ~
get[Option[Date]]("nicknames.start_date") map {
case id ~ name ~ startDate => Nickname(id, name, startDate)
}
}
val withTypeAndUser = simple ~ NickType.simple ~ User.simple map {
case nick ~ nType ~ user => nick.copy(nType = Some(nType), user = Some(user))
}
val withTypeAndEvents = withTypeAndUser ~ Event.simple map {
// it's wrong I think
case nick ~ event => nick.copy(events = Some(Set(event)))
}
}
// table types
case class NickType(id: Option[Long] = None, name: Option[String])
object NickType {
val simple = get[Option[Long]]("types.id") ~ get[Option[String]]("types.name") map {
case id ~ name => NickType(id, name)
}
}
// table events
case class Event(id: Option[Long] = None, regDate: Option[Date], description: Option[String],
user: Option[User] = None)
object Event {
val simple = {
get[Option[Long]]("events.id") ~
get[Option[Date]]("events.reg_date") ~
get[Option[String]]("events.description") map {
case id ~ regDate ~ description => Event(id, regDate, description)
}
}
}
I need that findById return my User with nicknames using eager fetch not lazy.
User -> Nicknames use One To Many relation
Nicknames -> User use Many To One relation join column user_id
Nicknames -> Events use One To Many relation
Events -> Nicknames use Many To One relation join column event_id
Related
I want to do a bulk insert using Slick 3.0 ++= function and also using returning to return the inserted objects.
I am wondering whether the return objects (Future[Seq[Something]]) has the same order as my arguments Seq[Something] (without id).
More specifically,
val personList: Seq[Person] = Seq(Person("name1"), Person("name2"), Person("name3"))
persons returning persons ++= personList
Is the result definitely be Future(Seq(Person(1, "name1"), Person(2, "name2"), Person(3, "name3")))? or can be in other result order?
Thanks.
Yes,I believe you are using auto incremented primary key.
I am also doing same as you have mentioned:
case class Person(name: String, id: Option[Int] = None)
class PersonTable(tag: Tag) extends Table[Person](tag, "person") {
val id = column[Int]("id", O.PrimaryKey, O.AutoInc)
val name = column[String]("name")
def * = (name, id.?) <> (Person.tupled, Person.unapply)
}
val personTableQuery = TableQuery[PersonTable]
def personTableAutoIncWithObject =
(personTableQuery returning personTableQuery.map(_.id)).into((person, id) => person.copy(id = Some(id)))
// insert all person without id and return all person with their id.
def insertAll(persons: List[Person]): Future[Seq[Person]] =
db.run { personTableAutoIncWithObject ++= persons }
//unit test for insertion order:
test("Add new persons ") {
val response = insertAll(List(Person("A1"), Person("A2"), Person("A3"), Person("A4"), Person("A5")))
whenReady(response) { persons =>
assert(persons === List(Person("A1", Some(1)), Person("A2", Some(2)), Person("A3", Some(3)),
Person("A4", Some(4)), Person("A5", Some(5))))
}
}
As per I know , the result of the batch inserts are in same order what you are sending to the database and "++=" function will return you the count of records inserted to the table.
I'm trying to insert if not exists, I found this post for 1.0.1, 2.0.
I found snippet using transactionally in the docs of 3.0.0
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)
I'm struggling to write the logic from insert if not exists with this structure. I'm new to Slick and have little experience with Scala. This is my attempt to do insert if not exists outside the transaction...
val result: Future[Boolean] = db.run(products.filter(_.name==="foo").exists.result)
result.map { exists =>
if (!exists) {
products += Product(
None,
productName,
productPrice
)
}
}
But how do I put this in the transactionally block? This is the furthest I can go:
val a = (for {
exists <- products.filter(_.name==="foo").exists.result
//???
// _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally
Thanks in advance
It is possible to use a single insert ... if not exists query. This avoids multiple database round-trips and race conditions (transactions may not be enough depending on isolation level).
def insertIfNotExists(name: String) = users.forceInsertQuery {
val exists = (for (u <- users if u.name === name.bind) yield u).exists
val insert = (name.bind, None) <> (User.apply _ tupled, User.unapply)
for (u <- Query(insert) if !exists) yield u
}
Await.result(db.run(DBIO.seq(
// create the schema
users.schema.create,
users += User("Bob"),
users += User("Bob"),
insertIfNotExists("Bob"),
insertIfNotExists("Fred"),
insertIfNotExists("Fred"),
// print the users (select * from USERS)
users.result.map(println)
)), Duration.Inf)
Output:
Vector(User(Bob,Some(1)), User(Bob,Some(2)), User(Fred,Some(3)))
Generated SQL:
insert into "USERS" ("NAME","ID") select ?, null where not exists(select x2."NAME", x2."ID" from "USERS" x2 where x2."NAME" = ?)
Here's the full example on github
This is the version I came up with:
val a = (
products.filter(_.name==="foo").exists.result.flatMap { exists =>
if (!exists) {
products += Product(
None,
productName,
productPrice
)
} else {
DBIO.successful(None) // no-op
}
}
).transactionally
It's is a bit lacking though, for example it would be useful to return the inserted or existing object.
For completeness, here the table definition:
case class DBProduct(id: Int, uuid: String, name: String, price: BigDecimal)
class Products(tag: Tag) extends Table[DBProduct](tag, "product") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column
def uuid = column[String]("uuid")
def name = column[String]("name")
def price = column[BigDecimal]("price", O.SqlType("decimal(10, 4)"))
def * = (id, uuid, name, price) <> (DBProduct.tupled, DBProduct.unapply)
}
val products = TableQuery[Products]
I'm using a mapped table, the solution works also for tuples, with minor changes.
Note also that it's not necessary to define the id as optional, according to the documentation it's ignored in insert operations:
When you include an AutoInc column in an insert operation, it is silently ignored, so that the database can generate the proper value
And here the method:
def insertIfNotExists(productInput: ProductInput): Future[DBProduct] = {
val productAction = (
products.filter(_.uuid===productInput.uuid).result.headOption.flatMap {
case Some(product) =>
mylog("product was there: " + product)
DBIO.successful(product)
case None =>
mylog("inserting product")
val productId =
(products returning products.map(_.id)) += DBProduct(
0,
productInput.uuid,
productInput.name,
productInput.price
)
val product = productId.map { id => DBProduct(
id,
productInput.uuid,
productInput.name,
productInput.price
)
}
product
}
).transactionally
db.run(productAction)
}
(Thanks Matthew Pocock from Google group thread, for orienting me to this solution).
I've run into the solution that looks more complete. Section 3.1.7 More Control over Inserts of the Essential Slick book has the example.
At the end you get smth like:
val entity = UserEntity(UUID.random, "jay", "jay#localhost")
val exists =
users
.filter(
u =>
u.name === entity.name.bind
&& u.email === entity.email.bind
)
.exists
val selectExpression = Query(
(
entity.id.bind,
entity.name.bind,
entity.email.bind
)
).filterNot(_ => exists)
val action = usersDecisions
.map(u => (u.id, u.name, u.email))
.forceInsertQuery(selectExpression)
exec(action)
// res17: Int = 1
exec(action)
// res18: Int = 0
according to the slick 3.0 manual insert query section (http://slick.typesafe.com/doc/3.0.0/queries.html), the inserted values can be returned with id as below:
def insertIfNotExists(productInput: ProductInput): Future[DBProduct] = {
val productAction = (
products.filter(_.uuid===productInput.uuid).result.headOption.flatMap {
case Some(product) =>
mylog("product was there: " + product)
DBIO.successful(product)
case None =>
mylog("inserting product")
(products returning products.map(_.id)
into ((prod,id) => prod.copy(id=id))) += DBProduct(
0,
productInput.uuid,
productInput.name,
productInput.price
)
}
).transactionally
db.run(productAction)
}
I have an issue with play framework and scala. My 2 Model class are below:
In here UserProfile have one Foriegn key to User Account and MyFriend hav 2 Foriegn key 1. user_Id from user profile and friend_ID from Userprofile
case class UserProfile(id: Pk[Long] = NotAssigned, useraccountid: Option[Long], name:
String, date_of_birth: Date, gender: String, image: String,status:String)
object UserProfile{
val simple = {
get[Pk[Long]]("user_profile.id") ~
get[Option[Long]]("user_profile.user_account_id") ~
get[String]("user_profile.name") ~
get[Date]("user_profile.date_of_birth") ~
get[String]("user_profile.gender") ~
get[String]("user_profile.image") ~
get[String]("user_profile.status") map {
case id ~ user_account_id ~ name ~ date_of_birth ~ gender ~ image ~ status =>
UserProfile(id, user_account_id, name, date_of_birth, gender, image,status )
}
}
/**
* Parse a userProfile from a MyFriend
*/
val withMyFriend =UserProfile.simple ~ (MyFriend.simple ?) map{
case userprofile ~ myfriend => (userprofile, myfriend)
}
/**
* Register a new useraccount.
*
* #param useraccount.
*/
def insert(userProfile: UserProfile): Long = {
DB.withConnection { implicit connection =>
SQL(
"""
insert into USER_PROFILE(USER_ACCOUNT_ID ,NAME,DATE_OF_BIRTH,GENDER,IMAGE,STATUS) values (
{user_account_id}, {name},{date_of_birth},{gender},{image},{status}
)
""").on(
'user_account_id -> userProfile.useraccountid.get,
'name -> userProfile.name,
'date_of_birth -> userProfile.date_of_birth,
'gender -> userProfile.gender,
'image -> userProfile.image,
'status -> userProfile.status).executeUpdate()
}
}
}
and
case class Post(id: Pk[Long]= NotAssigned, name:String, image:String, time:Date)
object Post{
/**
* Parse a Post from a Resultset
*/
val simple ={
get[Pk[Long]]("post.id") ~
get[String]("post.name")~
get[String]("post.image")~
get[Date]("post.time") map {
case id ~ name ~ image ~ time=> Post(id,name,image,time)
}
}
/**
* Parse a Post from a UserPost
*/
val withUserPost = Post.simple ~ (UserPost.simple ?) map{
case post ~ userpost => (post, userpost)
}
/**
* Register a new post.
*
* #param post
*/
def insert(post: Post): Long = {
DB.withConnection { implicit connection =>
SQL(
"""
insert into POST(NAME,IMAGE,TIME) values (
{name}, {image}, {time}
)
""").on(
'name -> post.name,
'image -> post.image,
'time -> post.time).executeUpdate()
}
}
/**
* Authonticate
*/
def authenticate(post: Post) = {
DB.withConnection { implicit connection =>
val postFound = SQL(
"""
select * from POST
where ID = (id}
""").on(
'Id -> post.id
).as(Post.simple.singleOpt)
postFound
}
}
}/** * Find Post With UserPost */ def userPost(post_id: Long) = { DB.withConnection { implicit connection => val userPost = SQL( """ select * from USER_POST where USER_POST_ID = {user_post_id} """).on( 'post_id -> post_id).as(UserPost.simple.singleOpt) userPost } }
and here is my controller application:
/* Authenticate User For CreatePost*/
def authendticatePost = Action { implicit request =>
val alert: Alert = new Alert("", "")
Common.setAlert(alert)
createPostForm.bindFromRequest.fold(
errors => BadRequest(views.html.createPost(errors, "There is some error")),
post => {
val postOpt = Post.authenticate(post)
postOpt match {
case None =>
val alert: Alert = new Alert("error", "Invalid Credentials")
Common.setAlert(alert)
val invalidCredentialsForm = Application.createPostForm.fill(Post(NotAssigned, post.name,post.image,post.time))
Ok(views.html.createPost(invalidCredentialsForm, "Invalid Credentials"))
case Some(authpost: Post) =>
val userSession = request.session + ("Id" -> authpost.id.toString)
val createPostOpt = Post.userPost(authpost.id.get)
createPostOpt match {
case None => Ok(views.html.createPost(Application.createPostForm, "")).withSession(userSession)
case Some(postFound: Post) =>
val createPostFormWithDetails = Application.createPostForm.fill(postFound)
Ok(views.html.createPost(createPostFormWithDetails, "")).withSession(userSession)
}
}
})
}
/**
Create Post*/
def createPost = Action { implicit request =>
val alert: Alert = new Alert("", "")
Common.setAlert(alert)
createPostForm.bindFromRequest.fold(
errors => BadRequest(views.html.createPost(errors, "There is some error")),
post => {
Post.findByPostName(post.name).isEmpty match {
case true =>
val createpost = Post(NotAssigned, post.name, post.image, post.time)
Post.insert(createpost)
Common.setAlert(alert)
case false =>
val createpost = Post(NotAssigned, post.name, post.image, post.time)
Post.insert(createpost)
Common.setAlert(alert)
}
Results.Redirect("/post")
})
}
/** Redirect To post**/
def post = Action { implicit request =>
Ok(views.html.createPost(Application.createPostForm, "post"))
}
and here is my routes:
POST /createPost controllers.Application.createPost
GET /post controllers.Application.post
and I've got this error at compile time:
pattern type is incompatible with expected type; found : object None required: Unit
: pattern type is incompatible with expected type;
found : object None
required: Unit
Error occurred in an application involving default arguments.
case None =>
^
I'm trying to select the coupled user by getting the correct linkedAccount.
The query that is created is correct but when trying to use a property
on dbuser e.g dbuser.lastName I get a compile error since dbuser is not
of type User but Query1 size=?
It's probably something really simple but I can't figure it out since I'm
a scala and squeryl noob!
Why doesn't it return the correct value and what have I done wrong in my query?
Also, saving works without any issues.
User:
class User(
#Column("id") val id: Long,
#Column("first_name") val firstName : String,
#Column("last_name") val lastName : String,
#Column("email") val email : String,
#Column("email_validated") val emailValidated: Boolean = false,
#Column("last_login") val lastLogin: Timestamp = null,
val created: Timestamp,
val modified: Timestamp,
val active: Boolean = false
) extends KeyedEntity[Long] {
lazy val linkedAccounts: OneToMany[LinkedAccount] = AppDB.usersToLinkedAccounts.left(this)
}
LinkedAccount:
class LinkedAccount(
#Column("id") val id: Long,
#Column("user_id") val userId: Long,
#Column("provider_user_id") val providerUserId: String,
#Column("salt") val salt: String,
#Column("provider_key") val providerKey: String) extends KeyedEntity[Long] {
lazy val user: ManyToOne[User] = AppDB.usersToLinkedAccounts.right(this)
}
AppDB:
object AppDB extends Schema {
val users = table[User]("users")
val linkedAccounts = table[LinkedAccount]("linked_account")
val usersToLinkedAccounts = oneToManyRelation(users, linkedAccounts).via((u, l) => u.id === l.userId)
def userByLinkedAccount(prodivderKey: String, providerUserId: String) = {
from(AppDB.users)(u =>
where(u.id in
from(AppDB.linkedAccounts)(la =>
where(la.userId === u.id and la.providerKey === prodivderKey and la.providerUserId === providerUserId)
select (la.userId)
)
)
select (u)
)
}
The call:
val dbuser = inTransaction {
val u2 = AppDB.userByLinkedAccount(id.providerId, id.id)
println(u2.statement)
}
println(dbuser.lastName)
The sql generated
Select
users10.last_login as users10_last_login,
users10.email as users10_email,
users10.modified as users10_modified,
users10.last_name as users10_last_name,
users10.first_name as users10_first_name,
users10.id as users10_id,
users10.created as users10_created,
users10.email_validated as users10_email_validated,
users10.active as users10_active
From
users users10
Where
(users10.id in ((Select
linked_account13.user_id as linked_account13_user_id
From
linked_account linked_account13
Where
(((linked_account13.user_id = users10.id) and (linked_account13.provider_key = 'facebook')) and (linked_account13.provider_user_id = 'XXXXXXXXXX'))
) ))
BTW, in the documentation to #Column and #ColumnBase it is said:
The preferred way to define column metadata is not not define them (!)
So, you can define columns just as
val id: Long,
instead of
#Column("id") val id: Long
Ok figured it out. I need to make the call, in this case:
.headOption
Also fixed the query after some tips from Per
def userByLinkedAccount(providerKey : String, providerUserId : String) = {
inTransaction {
from(AppDB.users, AppDB.linkedAccounts)((u,la) =>
where (u.id === la.userId and la.providerKey === providerKey and la.providerUserId === providerUserId)
select(u)
).headOption
}
}
Say that i have table such as:
UserActions
UserId INT
ActionDate TIMESTAMP
Description TEXT
that holds dates where users perfomed certainActions. If i wanted to get the last action that every user perfomed, i would have to do something like this in SQL:
SELECT *
FROM UserActions,
(
SELECT ua.UserId,
max(ua.ActionDate) AS lastActionDate
FROM UserActions ua
GROUP BY ua.UserId
) AS lastActionDateWithUserId
WHERE UserActions.UserId = lastActionDateWithUserId.UserId
AND UserActions.ActionDate = lastActionDateWithUserId.lastActionDate
Now, assume that i already have a table structure set up in scalaquery 0.9.5 for the UserActions such as:
case class UserAction(userId:Int,actionDate:Timestamp,description:String)
object UserActions extends BasicTable[UserAction]("UserActions"){
def userId = column[Int]("UserId")
def actionDate = column[Timestamp]("ActionDate")
def description = column[String]("Description")
def * = userId ~ actionDate ~ description <> (UserAction, UserAction.unapply _)
}
My question is: in ScalaQuery/SLICK how can i perform such a query?.
I have used Slick 1.0.0 with Scala 2.10.
I defined the objects like this:
case class UserAction(userId: Int, actionDate: Timestamp, description: String)
object UserActions extends Table[UserAction]("UserActions") {
def userId = column[Int]("UserId")
def actionDate = column[Timestamp]("ActionDate")
def description = column[String]("Description")
def * = userId ~ actionDate ~ description <> (UserAction, UserAction.unapply _)
}
Within session block
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
//...
}
I inserted some sample data
UserActions.insert(UserAction(10, timeStamp, "Action 1"))
UserActions.insert(UserAction(10, timeStamp, "Action 2"))
UserActions.insert(UserAction(10, timeStamp, "Action 3"))
UserActions.insert(UserAction(20, timeStamp, "Action 1"))
UserActions.insert(UserAction(20, timeStamp, "Action 2"))
UserActions.insert(UserAction(30, timeStamp, "Action 1"))
Query(UserActions).list foreach println
First thing to do is create the max query
// group by userId and select the userId and the max of the actionDate
val maxQuery =
UserActions
.groupBy { _.userId }
.map {
case (userId, ua) =>
userId -> ua.map(_.actionDate).max
}
The resulting query looks like this
val result =
for {
ua <- UserActions
m <- maxQuery
if (ua.userId === m._1 && ua.actionDate === m._2)
} yield ua
result.list foreach println