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

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)

Related

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

Slick, H2 insert query auto increment ID

I have this table in H2:
CREATE TABLE computer (id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, introduced TIMESTAMP, discontinued TIMESTAMP, company_id BIGINT, CONSTRAINT pk_computer PRIMARY KEY (id));
CREATE SEQUENCE computer_seq START WITH 1000;
Slick auto generated class for it:
case class ComputerRow(id: Long, name: String, introduced: Option[java.sql.Timestamp], discontinued: Option[java.sql.Timestamp], companyId: Option[Long])
/** GetResult implicit for fetching ComputerRow objects using plain SQL queries */
implicit def GetResultComputerRow(implicit e0: GR[Long], e1: GR[String], e2: GR[Option[java.sql.Timestamp]], e3: GR[Option[Long]]): GR[ComputerRow] = GR{
prs => import prs._
ComputerRow.tupled((<<[Long], <<[String], <<?[java.sql.Timestamp], <<?[java.sql.Timestamp], <<?[Long]))
}
/** Table description of table COMPUTER. Objects of this class serve as prototypes for rows in queries. */
class Computer(tag: Tag) extends Table[ComputerRow](tag, "COMPUTER") {
def * = (id, name, introduced, discontinued, companyId) <> (ComputerRow.tupled, ComputerRow.unapply)
/** Maps whole row to an option. Useful for outer joins. */
def ? = (id.?, name.?, introduced, discontinued, companyId).shaped.<>({r=>import r._; _1.map(_=> ComputerRow.tupled((_1.get, _2.get, _3, _4, _5)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
/** Database column ID PrimaryKey */
val id: Column[Long] = column[Long]("ID", O.PrimaryKey)
/** Database column NAME */
val name: Column[String] = column[String]("NAME")
/** Database column INTRODUCED */
val introduced: Column[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("INTRODUCED")
/** Database column DISCONTINUED */
val discontinued: Column[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("DISCONTINUED")
/** Database column COMPANY_ID */
val companyId: Column[Option[Long]] = column[Option[Long]]("COMPANY_ID")
/** Foreign key referencing Company (database name FK_COMPUTER_COMPANY_1) */
lazy val companyFk = foreignKey("FK_COMPUTER_COMPANY_1", companyId, Company)(r => r.id, onUpdate=ForeignKeyAction.Restrict, onDelete=ForeignKeyAction.Restrict)
}
/** Collection-like TableQuery object for table Computer */
lazy val Computer = new TableQuery(tag => new Computer(tag))
I can't seem to be able to insert a new row, however. The attempt below
val row = ("name", new Timestamp((new Date).getTime), new Timestamp((new Date).getTime), 123)
DB.withDynSession {
Computer.map( r =>
(r.name, r.introduced, r.discontinued, r.companyId)
) += row
}
Throws an error
[JdbcSQLException: NULL not allowed for column "ID"; SQL statement: INSERT INTO "COMPUTER" ("NAME","INTRODUCED","DISCONTINUED","COMPANY_ID") VALUES (?,?,?,?) [23502-175]]
The same approach works with MySQL and PostgreSQL, so I'm guessing that H2 doesn't have the same primary ID auto increment functionality? How do I make my insert work with slick then?
This is a working example of the same table with Anorm:
DB.withConnection { implicit connection =>
SQL(
"""
insert into computer values (
(select next value for computer_seq),
{name}, {introduced}, {discontinued}, {company_id}
)
"""
).on(
'name -> "name",
'introduced -> new Timestamp((new Date).getTime),
'discontinued -> new Timestamp((new Date).getTime),
'company_id -> 123
).executeUpdate()
}
As far as I can tell this is not a Slick problem, but your DDL statement doesn't specify auto increment for H2. Check the play-slick SQL code for the sample project: https://github.com/playframework/play-slick/blob/master/samples/computer-database/conf/evolutions/default/1.sql
Also check the H2 docs: http://www.h2database.com/html/grammar.html#column_definition
Try giving the case class a default value for id, like this:
case class ComputerRow(id: Long = 0, name: String, introduced: Option[java.sql.Timestamp], discontinued: Option[java.sql.Timestamp], companyId: Option[Long])
I'm not sure for H2 but when I'm working with Postgres I usually specify the default for the id field to comply to eventual checks form Slick, then on the database side the value insert is handled automatically by the sequence.
Edit:
I may be wrong on this but I noticed that you create a sequence without assigning it:
CREATE TABLE computer (id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, introduced TIMESTAMP, discontinued TIMESTAMP, company_id BIGINT, CONSTRAINT pk_computer PRIMARY KEY (id));
CREATE SEQUENCE computer_seq START WITH 1000;
In Postgres you create sequence and then assign them Like this:
create table users (
id bigint not null
);
create sequence users_seq;
alter table users alter column id set default nextval('users_seq');
in H2 as far as I can see this is how you assign an auto increment:
create table test(id bigint auto_increment, name varchar(255));
Taken form this SO question.

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.

[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 :)

Describing optional fields in Slick

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.