Describing optional fields in Slick - scala

The Slick DSL allows two ways to create optional fields in tables.
For this case class:
case class User(id: Option[Long] = None, fname: String, lname: String)
You can create a table mapping in one of the following ways:
object Users extends Table[User]("USERS") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def fname = column[String]("FNAME")
def lname = column[String]("LNAME")
def * = id.? ~ fname ~ lname <> (User, User.unapply _)
}
and
object Users extends Table[User]("USERS") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def fname = column[String]("FNAME")
def lname = column[String]("LNAME")
def * = id ~ fname ~ lname <> (User, User.unapply _)
}
}
What is the difference between the two? Is the one the old way and the other the new way, or do they serve different purposes?
I prefer the second choice where you define the identity as optional as part of the id definition because it's more consistent.

The .? operator in the first one allows you to defer the choice of having your field be optional to the moment of defining your projections. Sometimes that's not what you want, but defining your PK to be an Option is perhaps a bit funny because one might expect a PK to be NOT NULL.
You can use .? in additional projections besides *, for example:
def partial = id.? ~ fname
Then you could do Users.partial.insert(None, "Jacobus") and not worry about fields you're not interested in.

Related

What is the simplest way to handle a case class with a one to many relationship with Slick

I have the following case class
case class News(
newsId: Option[Long],
name: String,
description: String,
author: String,
creationDateTime: Option[OffsetDateTime],
images: Option[List[String]]
)
and would like to use Slick as a database mapping. I was able to create a working DataAccessObject and a NewsTable case class without the images: Option[List[String]]-field.
I dont really know how to approach my problem so i was hoping for a simple guide on how to handle one to many relationships with Slick. (Or does slick support a List of Strings out of the box)
I guess i should solve this problem with a tupledJoin but i can'tfigure it out.
I think that the given News case class violates the slick philosophie "With Slick it is advised to map one table to a tuple or case class without them having object references to related objects." but iam not sure about that. And i dont want to change this model because it is also part of my transfermodel.
Regards :)
There are many ways to model this into SQL depending on your requirement, but I am assuming a One-News-To-Many-Images relationship requirement.
Now you can model your SQL tables as follows
CREATE TABLE news (
id BIGINT NOT NULL AUTO_INCREMENT,
name TEXT NOT NULL,
/* ... other properties for news, but nothing for image */
PRIMARY KEY (id)
)
CREATE TABLE images (
id BIGINT NOT NULL AUTO_INCREMENT,
url TEXT NOT NULL
news_id BIGINT NOT NULL,
PRIMARY KEY (id),
/* you can decide if you need to a constraint or not */
FOREIGN KEY (news_id) REFERENCES news(id)
)
Now, your slick schema looks like
case class News(
id: Int,
name: String
)
case class Image(
id: Int,
url: String,
newsId: Int
)
class NewsTable(tag: Tag) extends Table[News](tag, "news") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (News.tupled, News.unapply)
}
class ImagesTable(tag: Tag) extends Table[Image](tag, "images") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def newsId = column[Int]("news_id")
def * = (id, name, newsId) <> (Image.tupled, Image.unapply)
}
val newsQuery = TableQuery[NewsTable]
val imagesQuery = TableQuery[ImagesTable]
Now you can get your news item and images for a news id = 22 by using following query
val query =
newsQuery
.joinLeft(imagesQuery)
.on({ case (news, image) => news.id === image.newsId })
.filter({ case (news, _) => news.id === 22 })
val queryAction = query.result.headOption
val newsOptionFuture = db.run(queryAction)

Play Slick 2.1.0 This DBMS allows only a single AutoInc column to be returned from an INSERT

In the following code I can insert my records just fine. But I would really like to get back the ID of the inserted value so that I can then return the object as part of my response.
def postEntry = DBAction { request =>
request.body.asJson.map {json =>
json.validate[(String, Long, String)].map {
case (name, age, lang) => {
implicit val session = request.dbSession
val something = Entries += Entry(None, name, age, lang)
Ok("Hello!!!: " + something)
}
}.recoverTotal {
e => BadRequest("Detected error: " + JsError.toFlatJson(e))
}
}.getOrElse {
BadRequest("Expecting Json data")
}
}
So I tried changing the insert to:
val something = (Entries returning Entries.map(_.id)) += Entry(None, name, age, lang)
But I get the following exception:
SlickException: This DBMS allows only a single AutoInc column to be returned from an INSERT
There is a note about it here: http://slick.typesafe.com/doc/2.1.0/queries.html
"Note that many database systems only allow a single column to be returned which must be the table’s auto-incrementing primary key. If you ask for other columns a SlickException is thrown at runtime (unless the database actually supports it)."
But it doesn't say how to just request the ID column.
Ende Nue above gave me the hint to find the problem. I needed to have the column marked primary key and auto increment in the table definition.
class Entries(tag: Tag) extends Table[Entry](tag, "entries") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def age = column[Long]("age")
def lang = column[String]("lang")
def * = (id, name, age, lang).shaped <> ((Entry.apply _)tupled, Entry.unapply _)
}
O.PrimaryKey, O.AutoInc

Scala meaning of tilde

Hi I new in Scala and have a problem with following example:
import scala.slick.driver.MySQLDriver.simple._
case class Customer(id: Option[Long], firstName: String, lastName: String, birthday: Option[java.util.Date])
/**
* Mapped customers table object.
*/
object Customers extends Table[Customer]("customers") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("first_name")
def lastName = column[String]("last_name")
def birthday = column[java.util.Date]("birthday", O.Nullable)
def * = id.? ~ firstName ~ lastName ~ birthday.? <>(Customer, Customer.unapply _)
implicit val dateTypeMapper = MappedTypeMapper.base[java.util.Date, java.sql.Date](
{
ud => new java.sql.Date(ud.getTime)
}, {
sd => new java.util.Date(sd.getTime)
})
val findById = for {
id <- Parameters[Long]
c <- this if c.id is id
} yield c
}
What is the meaning of line:
def * = id.? ~ firstName ~ lastName ~ birthday.? <>(Customer, Customer.unapply _)
How to interpret tilde signs and question marks?
You're looking at a Slick Table definition which follows the Slick 1.0+ version of defining the default projection of the Table using the method named *. The ~s join the Columns to make up the default view returned in a kind of projection builder pattern. The ?s indicate which fields represent Option values in the Customer class and <> is a method name in the Projection trait. You can think of the <> as being used to take things out or put things into the database for a Customer here. If you have something that doesn't map well, for example if that Table didn't have the implicit dateTypeMapper, the <> function is where you would manually adjust the values coming in and going out of the Customer case class for Date conversion.
Honestly, finding out where these methods come from is easier inside an IDE because the docs don't describe the class details and there are a lot of classes in the Slick scaladocs.
Here's a link to the 1.0.1 Lifted Embedded documentation.

How to return a sequence generation for an Id

In Scala Slick, if you are not using auto-incremented Id, but with sequence generation strategy for the id, how do you return that id?
Let's say you have the following case class and Slick table:
case class User(id: Option[Int], first: String, last: String)
object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id.? ~ first ~ last <> (User, User.unapply _)
}
The important things to consider here is the fact that User.id is an Option, because when we create it we will set it to None and the DB will generate the number for it.
Now you need to define a new insert mapping which omits the autoincremented column. This is needed because some databases don't allow you to insert into a column which is labeled as Auto Incremental. So instead of:
INSERT INTO users VALUES (NULL, "first, "last")
Slick will generate:
INSERT INTO user(first, last) VALUES ("first", "last")
The mapping looks like this (which must be placed inside Users):
def forInsert = first ~ last <> ({ t => User(None, t._1, t._2)}, { (u: User) => Some((u.first, u.last))})
Finally getting the auto-generated id is simple. We only need to specify in the returning the id column:
val userId = Users.forInsert returning Users.id insert User(None, "First", "Last")
Or you could instead move the returning statement:
def forInsert = first ~ last <> ({ t => User(None, t._1, t._2)}, { (u: User) => Some((u.first, u.last))}) returning id
And simplify your insert calls:
val userId = Users.forInsert insert User(None, "First", "Last")
Source

Trouble updating a record with Slick

With a class and table definition looking like this:
case class Group(
id: Long = -1,
id_parent: Long = -1,
label: String = "",
description: String = "")
object Groups extends Table[Group]("GROUPS") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def id_parent = column[Long]("ID_PARENT")
def label = column[String]("LABEL")
def description = column[String]("DESC")
def * = id ~ id_parent ~ label ~ design <> (Group, Group.unapply _)
def autoInc = id_parent ~ label ~ design returning id into {
case ((_, _, _), id) => id
}
}
To update a record, I can do this:
def updateGroup(id: Long) = Groups.where(_.id === id)
def updateGroup(g: Group)(implicit session: Session) = updateGroup(g.id).update(g)
But I can't get updates to work using for expressions:
val findGById = for {
id <- Parameters[Long]
g <- Groups; if g.id === id
} yield g
def updateGroupX(g: Group)(implicit session: Session) = findGById(g.id).update(g)
----------------------------------------------------------------------------^
Error: value update is not a member of scala.slick.jdbc.MutatingUnitInvoker[com.exp.Group]
I'm obviously missing something in the documentation.
The update method is supplied by the type UpdateInvoker. An instance of that type can be implicitly created from a Query by the methods productQueryToUpdateInvoker and/or tableQueryToUpdateInvoker (found in the BasicProfile), if they are in scope.
Now the type of your findById method is not a Query but a BasicQueryTemplate[Long, Group]. Looking at the docs, I can find no way from a BasicQueryTemplate (which is a subtype of StatementInvoker) to an UpdateInvoker, neither implicit nor explicit. Thinking about it, that makes kinda sense to me, since I understand a query template (invoker) to be something that has already been "compiled" from an abstract syntax tree (Query) to a prepared statement rather early, before parameterization, whereas an update invoker can only be built from an abstract syntax tree, i.e. a Query object, because it needs to analyze the query and extract its parameters/columns. At least that's the way it appears to work at present.
With that in mind, a possible solution unfolds:
def findGById(id: Long) = for {
g <- Groups; if g.id === id
} yield g
def updateGroupX(g: Group)(implicit session: Session) = findGById(g.id).update(g)
Where findById(id: Long) has the type Query[Groups, Group] which is converted by productQueryToUpdateInvoker to an UpdateInvoker[Group] on which the update method can finally be called.
Hope this helped.
Refer to http://madnessoftechnology.blogspot.ru/2013/01/database-record-updates-with-slick-in.html
I stuck with the updating today, and this blog post helped me much. Also refer to the first comment under the post.