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

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

Related

Scala/Slick working with additional columns in model that won't be persisited

I've been asking myself if there's a way to work with additional columns defined in the model that don't have corresponding columns in the db. For example: I have a table called List and another table called Item. The connection between those two table is List --N----------1-- Item. One List may have n items but one Item is only assigned to one List.
Those are the models:
As you can see the attribute list in my Item model is a foreign key to id on List.
The reason for me to want an extra attribute items on my List model that isn't represented in the db might become clear in this example:
def getListsByLC(lcId: Int): Action[AnyContent] = Action.async {
listRepo.getListsByLC(lcId).flatMap { lists =>
val newLists: Seq[Future[Seq[List]]] = lists.map(list => {
val items: Future[Seq[Item]] = itemRepo.getItemsByList(list.id).map { items =>
list.items.get ++ items //This is where the error occurs
}
items.map(i => lists)
})
Future.sequence(newLists).map(_.flatten).map(lists => Ok(Json.obj("lists" -> lists)))
}
}
Say I want to return all the Lists in my db with all the corresponding Items in it. I don't want to just pass the two seq and connect them in the Front-end. I'd like to add/merge the Seq[Item] to my items attribute in List.
This is how I map the attributes to my model:
private class ListTable(tag: Tag) extends Table[List](tag, "Lists") {
type Data = (Int, String, Date, Date, Int)
def constructList: Data => List = {
case (id, name, createdAt, updatedAt, lc) => List(id, name, createdAt, updatedAt, lc)
}
def extractList: PartialFunction[List, Data] = {
case List(id, name, createdAt, updatedAt, lc, _) => (id, name, createdAt, updatedAt, lc)
}
def id: Rep[Int] = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name: Rep[String] = column[String]("name")
def createdAt: Rep[Date] = column[Date]("createdAt")
def updatedAt: Rep[Date] = column[Date]("updatedAt")
def lc: Rep[Int] = column[Int]("lc")
def lcFK: ForeignKeyQuery[ListTable, List] = foreignKey("list_FK", lc, lists)(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
def * : ProvenShape[List] = (id, name, createdAt, updatedAt, lc) <> (constructList, extractList.lift)
}
My code compiles but throws an error at runtime:
Frankly, I have no idea what I'm doing wrong and I don't seem to find any other examples of people doing something similar. What I'm trying to do shouldn't be that hard, right? Am I missing something crucial? Any ideas? Thanks in advance!

Scala, Slick: How to use insert using auto generated number and byte array

object FingerprintsModel extends FingerprintDAO {
// Fingerprint class definition
class FingerprintsTable(tag: Tag) extends Table[Fingerprint](tag, "fingerprints") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def customerId = column[String]("customer_id", O.NotNull)
def template_one = column[Array[Byte]]("template_one", O.NotNull)
def template_two = column[Array[Byte]]("template_two", O.NotNull)
def created = column[DateTime]("created", O.NotNull)
def updated = column[Option[DateTime]]("updated")
def * = (id, customerId, template_one, template_two) <> (Fingerprint.tupled, Fingerprint.unapply _)
def fingerprint = foreignKey("CUSTOMER", customerId, CustomersModel.customers)(_.id)
}
and this is my insert statement:
FingerprintsModel.fingerprints.map(fi => (fi.customerId, fi.template_one, fi.template_two, fi.created))
.insert((id, fingerprint.template_one, fingerprint.template_two, new DateTime()))
Summary
There are two main modifications you need:
You will want a TableQuery[FingerprintsTable] to call insert (or += or ++=on); and
To get back the IDs inserted you need to use the returning method in Slick.
Worked example
It's hard to tell from the code you posted exactly what you have in mind. It would be helpful next time to simplify your example first.
I've assumed your model is something like this:
case class Fingerprint(
id: Long,
customerId: String,
template_one: Array[Byte]
)
I've left out one of the byte arrays and the created and updated fields as they don't seem relevant to the question. In other words, I've simplified.
The FingerprintTable seems ok. I'm ignoring the foreign key as that doesn't seem relevant. Oh, the O.NotNull are now deprecated (in Slick 3 at least). You can leave them off because your columns are not Option values.
What we need is the table query, which I'd add inside FingerprintsModel:
lazy val fingerprints = TableQuery[FingerprintsTable]
lazy val fingerprintsWithID = fingerprints returning fingerprints.map(_.id)
You could use fingerprints to insert data. But you've asked for the IDs back, so you want to use fingerprintsWithID.
Putting it all together (again, using Slick 3 here):
object FingerprintExample extends App {
import FingerprintsModel._
val testData = Seq(
Fingerprint(0L, "Alice", Array(0x01, 0x02)),
Fingerprint(0L, "Bob", Array(0x03, 0x04))
)
// A program that will create the schema, and insert the data, returning the IDs
val program = for {
_ <- fingerprints.schema.create
ids <- fingerprintsWithID ++= testData
} yield ids
// Run the program using an in-memory database
val db = Database.forConfig("h2mem1")
val future = db.run(program)
val result = Await.result(future, 10 seconds)
println(s"The result is: $result")
}
Produces:
The result is: List(1, 2)

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

[SlickException: Read NULL value for column (USERS /670412212).LOGIN_ID]

I am using Slick 1.0.0 with play framework 2.1.0. I am getting the following error when I query my Users table. The value of LOGIN_ID is null in DB.
The query I am executing is:
val user = { for { u <- Users if u.providerId === id.id } yield u}.first
This results in the following error:
play.api.Application$$anon$1: Execution exception[[SlickException: Read NULL value for column (USERS /670412212).LOGIN_ID]]
at play.api.Application$class.handleError(Application.scala:289) ~[play_2.10.jar:2.1.0]
at play.api.DefaultApplication.handleError(Application.scala:383) [play_2.10.jar:2.1.0]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$12$$anonfun$apply$24.apply(PlayDefaultUpstreamHandler.scala:314) [play_2.10.jar:2.1.0]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$12$$anonfun$apply$24.apply(PlayDefaultUpstreamHandler.scala:312) [play_2.10.jar:2.1.0]
at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:113) [play_2.10.jar:2.1.0]
at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:113) [play_2.10.jar:2.1.0]
scala.slick.SlickException: Read NULL value for column (USERS /670412212).LOGIN_ID
at scala.slick.lifted.Column$$anonfun$getResult$1.apply(ColumnBase.scala:29) ~[slick_2.10-1.0.0.jar:1.0.0]
at scala.slick.lifted.TypeMapperDelegate$class.nextValueOrElse(TypeMapper.scala:158) ~[slick_2.10-1.0.0.jar:1.0.0]
at scala.slick.driver.BasicTypeMapperDelegatesComponent$TypeMapperDelegates$StringTypeMapperDelegate.nextValueOrElse(BasicTypeMapperDelegatesComponent.scala:146) ~[slick_2.10-1.0.0.jar:1.0.0]
at scala.slick.lifted.Column.getResult(ColumnBase.scala:28) ~[slick_2.10-1.0.0.jar:1.0.0]
at scala.slick.lifted.Projection15.getResult(Projection.scala:627) ~[slick_2.10-1.0.0.jar:1.0.0]
at scala.slick.lifted.Projection15.getResult(Projection.scala:604) ~[slick_2.10-1.0.0.jar:1.0.0]
My User table is defined as :
package models
import scala.slick.driver.MySQLDriver.simple._
case class User(userId:String,email:String,loginId:String,fullName:String,firstName:String,lastName:String,location:String,homeTown:String,providerId:String,provider:String,state:String,zip:String,accessKey:String,refreshKey:String,avatarUrl:String)
object Users extends Table[User]("USERS") {
def userId = column[String]("USER_ID", O.PrimaryKey) // This is the primary key column
def email = column[String]("EMAIL",O.NotNull)
def loginId = column[String]("LOGIN_ID",O.Nullable)
def fullName = column[String]("FULL_NAME",O.NotNull)
def firstName = column[String]("FIRST_NAME",O.Nullable)
def lastName = column[String]("LAST_NAME",O.Nullable)
def location = column[String]("LOCATION",O.Nullable)
def homeTown = column[String]("HOME_TOWN",O.Nullable)
def providerId = column[String]("PROVIDER_ID",O.Nullable)
def provider = column[String]("PROVIDER",O.Nullable)
def state = column[String]("STATE",O.Nullable)
def zip = column[String]("ZIP",O.Nullable)
def accessKey = column[String]("ACCESS_KEY",O.Nullable)
def refreshKey = column[String]("REFRESH_KEY",O.Nullable)
def avatarUrl = column[String]("AVATAR_URL",O.Nullable)
// Every table needs a * projection with the same type as the table's type parameter
def * = userId ~ email ~ loginId ~ fullName ~ firstName ~ lastName ~ location ~ homeTown ~ providerId ~ provider ~ state ~ zip ~ accessKey ~ refreshKey ~ avatarUrl <> (User,User.unapply _)
}
Please help. It looks like Slick can not handle Null values from DB?
Your case class is not ok. If you use O.Nullable, all your properties have to be Option[String].
If you get this error, you'll have to either make the properties O.Nullable, or you have to specify that your query returns an option.
For example let's say you do a rightJoin you might not want to make the properties of the right record optional. In that case you can customize the way you yield your results using .?
val results = (for {
(left, right) <- rightRecord.table rightJoin leftRecord.table on (_.xId === _.id)
} yield (rightRecord.id, leftRecord.name.?)).list
results map (r => SomeJoinedRecord(Some(r._1), r._2.getOrElse(default)))
This problem arises if a column contains a null value and at runtime it gets a null in the column response. If you see in the code below, my cust_id is nullable, but it has no null values. Since, there is a job that makes sure that is is never null. So, the below mapping works. However, it is the best practice to look at your table structure and create the class accordingly. This avoids nasty runtime exception.
If the table definition on database is like:
CREATE TABLE public.perf_test (
dwh_id serial NOT NULL,
cust_id int4 NULL,
cust_address varchar(30) NULL,
partner_id int4 NULL,
CONSTRAINT perf_test_new_dwh_id_key UNIQUE (dwh_id)
);
The corresponding class definition can be as below. But, it will be advised to have the cust_id also as Option[Int]. However, as long as it has values and no nulls, you will not encounter error.
import slick.jdbc.PostgresProfile.api._
class PerfTest(tag: Tag) extends Table[(Int, Int, Option[String], Option[Int])](tag, "perf_test") {
def dwhId = column[Int]("dwh_id")
def custId = column[Int]("cust_id")
def custAddress = column[Option[String]]("cust_address")
def partnerId = column[Option[Int]]("partner_id")
def * = (dwhId, custId, custAddress,partnerId)
}
What happened to me was that I had anomalies in the DB and some values were accidentally nulls - those that I didn't expect to be. So do not forget to check your data too :)

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.