How can I map inherited model to entity using Scala and Squeryl? - scala

I have the model User on my system that has a OneToMany relationship with Role, role is currently a trait and I have other roles inherinting from it: AdministratorRole, VisitorRole, ResidentRole. My doubt is how can I model (best way) this system to my entities database. Below my current "Role" models:
PS: Ignore the //TODOS
trait Role extends KeyedEntity[Long]
{
val id:Long = 0
val identifier = ""
val idMetaData:Long = 0
//val permissions = List[Permission]()
}
class AdministratorRole(val idCondo:Long) extends Role {
override val identifier = "AdministratorRole"
//TODO Map relationships
lazy val fromCondo:Condo = null
}
class ResidentRole(override val id:Long, val idUnit:Long) extends Role{
override val identifier = "ResidentRole"
//TODO Map relationships
lazy val fromHousingUnit:HousingUnit = null
}
class VisitorRole(val idFromUser:Long) extends Role {
override val identifier = "VisitorRole"
//TODO Map relationships
lazy val fromUser = null
}
And here my current "Role" entities:
create table Roles(
id int primary key,
identifier varchar(255) NOT NULL,
idUser int NOT NULL REFERENCES Users(id)
);
create table AdministratorRoles(
idRole int NOT NULL UNIQUE REFERENCES Roles(id),
idCondo int NOT NULL REFERENCES Condos(id)
);
create table ResidentRoles(
idRole int NOT NULL UNIQUE REFERENCES Roles(id),
idUnit int NOT NULL REFERENCES Units(id)
);
create table VisitorRoles(
idRole int NOT NULL UNIQUE REFERENCES Roles(id),
idFromUser int NOT NULL REFERENCES Users(id)
);
How can I map these models/entities? I'm really having a hard time. Since now, thanks for the help.

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)

Squeryl: Nullable foreign key

I am trying to declare a schema, where some entity has recursive relation (property parent_id may be either NULL or some value from the same table):
class Merchant(
val id: Int = 0,
#Column("parent_id") var parentId: Option[Int] = None
) extends KeyedEntity[Int] {
def this() = this(0, Some(0))
}
in schema:
val merchants: Table[Merchant] = table[Merchant]
on(merchants)(m => declare(
m.id is autoIncremented
))
val parent2merchants = oneToManyRelation(merchants, merchants).via((p, m) => p.id === m.parentId)
The table gets created, and column parent_id int (allows NULLs).
But when I'm trying to add records leaving parentId = None, I get an error:
Referential integrity constraint violation: "MERCHANTSFK1: PUBLIC.MERCHANTS FOREIGN KEY(PARENT_ID) REFERENCES PUBLIC.MERCHANTS(ID)"; SQL statement:
insert into merchants (parent_id) values (?) [23002-127]
errorCode: 23002, sqlState: 23002
jdbcParams:[0]
So, for some reason, parentId instead of NULL gets 0. What am I doing wrong?

Join on two foreign keys from same table in scalikejdbc

So i have a one table that has two FK that points at same table.
For example:
Message table with columns sender and receiver that both references id in user table.
When i'm writing query to fetch message and join on both the result is same use for both, the first one.
Here is how i'm trying to do it.
import scalikejdbc._
Class.forName("org.h2.Driver")
ConnectionPool.singleton("jdbc:h2:mem:hello", "user", "pass")
implicit val session = AutoSession
sql"""
create table members (
id serial not null primary key,
name varchar(64),
created_at timestamp not null
)
""".execute.apply()
sql"""
create table message (
id serial not null primary key,
msg varchar(64) not null,
sender int not null,
receiver int not null
)
""".execute.apply()
Seq("Alice", "Bob", "Chris") foreach { name =>
sql"insert into members (name, created_at) values (${name}, current_timestamp)".update.apply()
}
Seq(
("msg1", 1, 2),
("msg2", 1, 3),
("msg3", 2, 1)
) foreach { case (m, s, r) =>
sql"insert into message (msg, sender, receiver) values (${m}, ${s}, ${r})".update.apply()
}
import org.joda.time._
case class Member(id: Long, name: Option[String], createdAt: DateTime)
object Member extends SQLSyntaxSupport[Member] {
override val tableName = "members"
def apply(mem: ResultName[Member])(rs: WrappedResultSet): Member = new Member(
rs.long("id"), rs.stringOpt("name"), rs.jodaDateTime("created_at"))
}
case class Message(id: Long, msg: String, sender: Member, receiver: Member)
object Message extends SQLSyntaxSupport[Message] {
override val tableName = "message"
def apply(ms: ResultName[Message], s: ResultName[Member], r: ResultName[Member])(rs: WrappedResultSet): Message = new Message(
rs.long("id"), rs.string("msg"), Member(s)(rs), Member(r)(rs))
}
val mem = Member.syntax("m")
val s = Member.syntax("s")
val r = Member.syntax("r")
val ms = Message.syntax("ms")
val msgs: List[Message] = sql"""
select *
from ${Message.as(ms)}
join ${Member.as(s)} on ${ms.sender} = ${s.id}
join ${Member.as(r)} on ${ms.receiver} = ${r.id}
""".map(rs => Message(ms.resultName, s.resultName, r.resultName)(rs)).list.apply()
Am I doing something wrong or is it bug?
Sorry for late reply. We have the Google Group ML and I actively read notifications from the group.
When you're in a hurry, please post stackoverflow URLs there. https://groups.google.com/forum/#!forum/scalikejdbc-users-group
In this case, you need to write select ${ms.result.*}, ${s.result.*} instead of select *. Please read this page for details. http://scalikejdbc.org/documentation/sql-interpolation.html

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.

Anorm string set from postgres ltree column

I have a table with one of the columns having ltree type, and the following code fetching data from it:
SQL("""select * from "queue"""")()
.map(
row =>
{
val queue =
Queue(
row[String]("path"),
row[String]("email_recipients"),
new DateTime(row[java.util.Date]("created_at")),
row[Boolean]("template_required")
)
queue
}
).toList
which results in the following error:
RuntimeException: TypeDoesNotMatch(Cannot convert notification.en.incident_happened:class org.postgresql.util.PGobject to String for column ColumnName(queue.path,Some(path)))
queue table schema is the following:
CREATE TABLE queue
(
id serial NOT NULL,
template_id integer,
template_version integer,
path ltree NOT NULL,
json_params text,
email_recipients character varying(1024) NOT NULL,
email_from character varying(128),
email_subject character varying(512),
created_at timestamp with time zone NOT NULL,
sent_at timestamp with time zone,
failed_recipients character varying(1024),
template_required boolean NOT NULL DEFAULT true,
attachments hstore,
CONSTRAINT pk_queue PRIMARY KEY (id ),
CONSTRAINT fk_queue__email_template FOREIGN KEY (template_id)
REFERENCES email_template (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT
)
WITH (
OIDS=FALSE
);
ALTER TABLE queue
OWNER TO postgres;
GRANT ALL ON TABLE queue TO postgres;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE queue TO writer;
GRANT SELECT ON TABLE queue TO reader;
Why is that? Isn't notification.en.incident_happened just an ordinary string? Or am I missing anything?
UPD:
The question still applies, but here is a workaround:
SQL("""select id, path::varchar, email_recipients, created_at, template_required from "queue"""")()
This looked like a fun project so I implemented the ltree column mapper.
I piggybacked off anorm-postgresql, since that project already implements some postgres types in anorm. It looks good, and it would be useful if it implemented the full range of postgres types. My code has been merged in, so you can use that library. Alternatively, just use the following code:
import org.postgresql.util.PGobject
import anorm._
object LTree {
implicit def rowToStringSeq: Column[Seq[String]] = Column.nonNull { (value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case pgo:PGobject => {
val seq = pgo.getValue().split('.')
Right(seq.toSeq)
}
case x => Left(TypeDoesNotMatch(x.getClass.toString))
}
}
implicit def stringSeqToStatement = new ToStatement[Seq[String]] {
def set(s: java.sql.PreparedStatement, index: Int, aValue: Seq[String]) {
val stringRepresentation = aValue.mkString(".")
val pgo:org.postgresql.util.PGobject = new org.postgresql.util.PGobject()
pgo.setType("ltree");
pgo.setValue( stringRepresentation );
s.setObject(index, pgo)
}
}
}
Then you can map an ltree to a Seq[String]. Notice that it is a sequence of path elements order matters so it is a Seq[String], rather than String or Set[String]. If you want a single string just say path.mkString("."). Usage below:
import LTree._
SQL("""select * from "queue"""")()
.map(
row =>
{
val queue =
Queue(
row[Seq[String]]("path"),
row[String]("email_recipients"),
new DateTime(row[java.util.Date]("created_at")),
row[Boolean]("template_required")
)
queue
}
).toList