Typed projection with Slick - scala

I'm trying to define a Type on the query side to map my join so that I can avoid returning a tuple of values that I have to manually apply to my projection case class post query.
Given a relationship like:
case class Parent(id: Int, name: String, extra: String)
class ParentTable(tag: Tag) extends Table[Parent](tag, "parent") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def extra = column[String]("extra")
def * = (id, name, extra) <> (Parent.tupled, Parent.unapply)
}
val parents = TableQuery[ParentTable]
case class Child(id: Int, parentId: Int, name: String, extra: String)
class ChildTable(tag: Tag) extends Table[Child](tag, "child") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def parentId = column[Int]("parent_id")
def parent = foreignKey("parent_fk", parentId, parents)(_.id)
def name = column[String]("name")
def extra = column[String]("extra")
def * = (id, parentId, name, extra) <> (Child.tupled, Child.unapply)
}
val children = TableQuery[ChildTable]
I want to project into a case class like:
case class ChildWithParentName(id: Int, name: String, parentName: String)
The join and projection looks like:
val q = for {
c <- children
p <- parents if c.parentId === p.id
} yield (c.id,c.name,p.name)
I put this in a function and allow children and parents to be parameterized. The function doesn't run the query, because sometimes I want .result and sometimes I want .result.headOption, so my function signature is:
Query[(Rep[Int], Rep[String], Rep[String]), (Int, String, String), Seq]
I would like to create a Type on the Query side with a shape something like:
class ChildParentProjection(val id: Rep[Int],
val name: Rep[String],
val parentName[String])
so that I could get a function signature like:
Query[ChildParentProjection, ChildWithParentName, Seq]
Is that possible in slick?

I don't really understand why you would like to use the class ChildParentProjection.
if you want to return a Seq[ChildWithParentName] when executing result on the query, you'll have to map the tuple resulting from your monadic join to the ChildWithParentName class like this :
val q = for {
c <- children
p <- parents if c.parentId === p.id
} yield (c.id,c.name,p.name) <> (ChildWithParentName.tupled,ChildWithParentName.unapply)
I wish I have understood your question

Related

Slick join two tables and get result of both

I have a Many to Many relationship setup like this:
Person <-> PersonField <-> Field
Now I want to query not only all the fields of a Person (I can do that), but a joined version of PersonField with Field of a Person. (I want to query/retrieve the Information in the Pivot/Intermediate Table "PersonField" as well!)
Person:
case class Person(id: Long, name: String)
{
def fields =
{
person <- Persons.all.filter(_.id === this.id)
field <- person.fields
} yield field
}
class Persons(tag: Tag) extends Table[Person](tag, "persons")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (Person.tupled, Person.unapply)
def fields = PersonFields.all.filter(_.personID === id).flatMap(_.fieldFK)
}
object Persons
{
lazy val all = TableQuery[Persons]
}
Field:
case class Field(id: Long, name: String, description: Option[String])
class Fields(tag: Tag) extends Table[Field](tag, "fields")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def description = column[Option[String]]("description")
def * = (id, name, description) <> (Field.tupled, Field.unapply)
}
object Fields
{
lazy val all = TableQuery[Fields]
}
PersonField:
case class PersonField(id: Long, personID: Long, fieldID: Long, value: String)
// TODO add constraint to make (personID, fieldID) unique
class PersonFields(tag: Tag) extends Table[PersonField](tag, "person_field")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def personID = column[Long]("person_id")
def fieldID = column[Long]("field_id")
def value = column[String]("value")
def * = (id, personID, fieldID, value) <> (PersonField.tupled, PersonField.unapply)
def personFK = foreignKey("person_fk", personID, Persons.all)(_.id)
def fieldFK = foreignKey("field_fk", fieldID, Fields.all)(_.id)
}
object PersonFields
{
lazy val all = TableQuery[PersonFields]
}
Now to query all the fields of a Person I have a little helper-class:
def getFields(p: Person): Future[Seq[Field]] =
{
val query = p.fields
db.run(query.result)
}
So I can do
val personX ...
personX.onSuccess
{
case p: Person =>
{
val fields = helper.getFields(p)
fields.onSuccess
{
case f: Seq[Field] => f foreach println
}
}
}
Now each field of personX gets printed to the console. Works like a charm.
The thing is, I want to get the PersonField as well (with the Field)!
So I tried the following changes (among others that didn't work, which I can't remember)
In Person:
def fields =
{
for
{
person <- Persons.all.filter(_.id === this.id)
field <- person.fields join Fields.all on (_.fieldID === _.id)
} yield field
}
In PersonS
def fields = PersonFields.all.filter(_.personID === id) // No flatMap here!
then getFields(p: Person) looks like this:
def getFields(p: Person): Future[Seq[(PersonField, Field)]]
but
personX.onSuccess
{
case p: Person =>
{
val fields = helper.getFields(p)
fields.onSuccess
{
case f: Seq[(PersonField, Field)] => f map(f => println(f._1)}
}
}
}
gives me nothing, so I guess my join must be wrong. But what exactly am I doing wrong?
You can join all three, then yield the result
for {
((personField, person), field) <- PersonFields.all join Persons.all on (_.personId === _.id) join Fields.all on (_._1.fieldId === _.id)
if person.id === this.id
} yield (personField, person, field)
(I am not sure I got exactly what you were trying to get out of the query, so you can just edit the yield part )

Slick and nesting case classes a no go?

I am using slick (and the play framework) to build an application on top of an existing database. I cannot change the database structure.
My database has the following 2 tables:
Meeting:
id (PK)
name
chairman_id (FK to Person.id)
houseman_id (FK to Person.id)
Person:
id (pk)
first_name
last_name
I wanted to define my case classes like this:
case class Meeting (
id: Int,
name: String,
chairman: Person,
houseman: Person
)
case class Person (
id: Int,
firstName: String,
lastName: String
)
But from the very minimal slick documentation around this, it looks like I have to keep the ids in the case class rather than using "Person". Is that correct?
Whats the best approach for this? Sorry for the relatively open question, very new to scala, slick and play.
Thanks,
Ed
You have foreign keys, they don't translate to case classes, they translate to ids:
case class Meeting (
id: Int,
name: String,
chairmanId: Int,
housemanId: Int)
case class Person (
id: Int,
firstName: String,
lastName: String)
And the schema would be something like:
case class Meeting (
id: Int,
name: String,
chairmanId: Int,
housemanId: Int)
case class Person (
id: Int,
firstName: String,
lastName: String)
class Meetings(tag: Tag) extends Table[Meeting](tag, "meeting") {
def * = (id, name, chairmanId, housemanId) <>(Meeting.tupled, Meeting.unapply)
def ? = (id.?, name, chairmanId, housemanId).shaped.<>({
r => import r._
_1.map(_ => Meeting.tupled((_1.get, _2, _3, _4)))
}, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
val name: Column[String] = column[String]("name")
val chairmanId: Column[Int] = column[Int]("chairmanId")
val housemanId: Column[Int] = column[Int]("housemanId")
lazy val meetingChairmanFk =
foreignKey("meeting_chairman_fk", chairmanId, persons)(r => r.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
lazy val meetingHousemanFk =
foreignKey("meeting_houseman_fk", housemanId, persons)(r => r.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
}
lazy val meetings = new TableQuery(tag => new Meetings(tag))
class Persons(tag: Tag) extends Table[Person](tag, "person") {
def * = (id, firstName, lastName) <>(Person.tupled, Person.unapply)
def ? = (id.?, firstName, lastName).shaped.<>({
r => import r._
_1.map(_ => Person.tupled((_1.get, _2, _3)))
}, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
val firstName: Column[String] = column[String]("firstname")
val lastName: Column[String] = column[String]("lastname")
}
lazy val persons = new TableQuery(tag => new Persons(tag))
And it could be used like this:
val thisMeeting = meetings.filter(_.name === "thisMeeting").join(persons).on((m, p) => m.housemanId === p.id || m.chairmanId === p.id).list()
Or using for comprehension (which I personally find more legible):
val thatMeething = (for {
m <- meetings
p <- persons if (p.id === m.chairmanId || p.id === m.housemanId) && m.name === "thatMeeting"
} yield m.id).run
Note that the second query corresponds to an implicit inner join, other types of join are also supported, you can find them here.

how to make the Query to list in slick2?

My table:
case class Subject(id: Int, name:String, describe: String, sub_resource:String, addId:Long, recommand:Int, commentsum :Int, commentnumber: Int, userId: Int)
class Subjects(tag: Tag) extends Table[Subject](tag, "Subject") {
def id=column[Int]("ID", O.PrimaryKey)
def name=column[String]("Name")
def describe=column[String]("describe")
def sub_resource=column[String]("Resource")
def keywords=column[String]("Keyword")
def addID=column[Long]("Address")
def recommandrate=column[Int]("Recommand")
def commentsum=column[Int]("Sum_of_rate")
def commentnumber=column[Int]("Rate_number")
def userId=column[Int]("owner")
def uniqueName = index("idx_grp_name", name, unique = true)
def * = (id, name,sub_resource,keywords, addID, recommandrate, commentsum, commentnumber,userId)<> (Subject.tupled, Subject.unapply)
def sub_res=foreignKey("sub_res_FK", sub_resource, resource)(_.link)
def sub_address=foreignKey("sub_add_FK", addID, address)(_.id)
def sub_user=foreignKey("sub_user_FK", userId, user)(_.id)
}
val subject = TableQuery[Subjects]
I want get the list name contain "USA" and "China":
How to write the filter and list the name, userId and describe?
I want to use subject.filter(....).....
you need both a filter (to filter on USA and China) and a map to transform to name, userId, and decribe so something like (untested)
subject.filter(i => i.name === "USA" || i.name === "China")
.map(i => (i._2, i._10, i._3)
as the result is a list of tuples you have to access the fields with the ._[index] thingie
[update]
I see not all your fields are in your def * = ... so that's also something to look at
Edited by cvogt: I corrected the Syntax. It's just like using Scala collections, except for the ===.

How to combine multiple columns in one case class field when using lifted embedding?

We have a MySQL table containing several boolean columns which specify the roles a user may have. Is it possible with slick's lifted embedding to write a type mapper which combines & transforms these multiple columns to one field in the case class User like shown below?
case class User(id: Option[Int], nickname: String, role: Seq[Role.Role])
object Users extends Table[(User)]("ask_user") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def nickname = column[String]("nickname")
def is_editor = column[Boolean]("is_editor")
def is_moderator = column[Boolean]("is_moderator")
def is_administrator = column[Boolean]("is_administrator")
def is_usermoderator = column[Boolean]("is_usermoderator")
def is_usermoderator2 = column[Boolean]("is_usermoderator2")
def is_partner = column[Boolean]("is_partner")
def is_premium_partner = column[Boolean]("is_premium_partner")
def is_corporate_paid = column[Boolean]("is_corporate_paid")
}
You can provide your own constructor and extractor functions for User objects to Slick using the <> function on the * projection. Something like this:
class User extends Table[User](...){
...
def * = col1 ~ col2 ~ col3 ~ col4 <> (constructUser, extractUser)
}
def constructUser( col1: T1, col2: T2, col3: T3, col4: T4 )
= User(col1, Roles(col2, col3, col4))
def extractUser( user: User ) = user match{
case User(col1, Roles(col2, col3, col4)) => (col1, col2, col3, col4)
}
object Roles{
def apply( col2: T2, col3: T3, col4: T4 ) : Set[Role] = ...
def unapply( roles: Set[Role] ) : Option[(T2, T3, T4)] = ...
}
Also see http://slick.typesafe.com/doc/1.0.1/lifted-embedding.html#mapped-tables
This is possible, you can use an Enumeration or roles:
object Role extends Enumeration with BitmaskedEnumeration {
type Role = Value
val Editor, Moderator, Administrator, Usermoderator, Usermoderator2,
Partner, PremiumPartner, CorporatePaid = Value
}
import Role._
A field of type Role can then be used in your Slick Table "as is", like any of the native types.
Here is the code for BitmaskedEnumeration:
import slick.jdbc.GetResult
import slick.lifted.{BaseTypeMapper, MappedTypeMapper}
/** Source: https://github.com/nafg/slick-additions. */
trait Bitmasked {
type Value
def bitFor: Value => Int
def forBit: Int => Value
def values: Iterable[Value]
def longToSet: Long => Set[Value] =
bm => values.toSeq.filter(v => 0 != (bm & (1 << bitFor(v)))).toSet
def setToLong: Set[Value] => Long =
_.foldLeft(0L){ (bm, v) => bm + (1L << bitFor(v)) }
implicit lazy val enumTypeMapper: BaseTypeMapper[Value] =
MappedTypeMapper.base[Value, Int](bitFor, forBit)
implicit lazy val enumSetTypeMapper: BaseTypeMapper[Set[Value]] =
MappedTypeMapper.base[Set[Value], Long](setToLong, longToSet)
implicit lazy val getResult: GetResult[Value] =
GetResult(r => forBit(r.nextInt))
implicit lazy val getSetResult: GetResult[Set[Value]] =
GetResult(r => longToSet(r.nextLong))
}
/** Mix this class into a subclass of Enumeration to have it usable as a
* column type in a Slick Table. */
trait BitmaskedEnumeration extends Bitmasked { this: Enumeration =>
def bitFor = _.id
def forBit = apply(_)
}

SLICK How to define bidirectional one-to-many relationship for use in case class

I am using SLICK 1.0.0-RC2. I have defined the following two tables Directorate and ServiceArea where Directorate has a one to many relationship with ServiceArea
case class Directorate(dirCode: String, name: String)
object Directorates extends Table[Directorate]("DIRECTORATES") {
def dirCode = column[String]("DIRECTORATE_CODE", O.PrimaryKey)
def name = column[String]("NAME")
def * = dirCode ~ name <> (Directorate, Directorate.unapply _)
}
case class ServiceArea(areaCode: String, dirCode: String, name: String)
object ServiceAreas extends Table[ServiceArea]("SERVICE_AREAS") {
def areaCode = column[String]("AREAE_CODE", O.PrimaryKey)
def dirCode = column[String]("DIRECTORATE_CODE")
def name = column[String]("NAME")
def directorate = foreignKey("DIR_FK", dirCode, Directorates)(_.dirCode)
def * = areaCode ~ dirCode ~ name <> (ServiceArea, ServiceArea.unapply _)
}
To make the Directorate case class useful in my Play application form I am trying to redefine the Directorate case class to have a Seq of ServiceAreas that are related to that Directorate.
case class Directorate(dirCode: String, name: String, serviceAreas: Seq[ServiceArea])
My problem is now with the Directorate table projection. I have attempted to create a method in Directorates:
def serviceAreas = (for { a <- ServiceAreas
if (a.dirCode === dirCode)
} yield (a)).list map {
case t: ServiceArea => t
}
so that I can try something like
def * = dirCode ~ name ~ serviceAreas <> (Directorate, Directorate.unapply _)
but this cannot not work as serviceAreas only goes one way.
It seems reasonable to me that for the Directorate case class to be a useful domain object that it should be able contain the related ServiceAreas.
I'm wondering how I should traverse the inverse relationship so that Directorate table projection will work.
I'm sure there is a more elegant solution, but this should do the trick:
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
object SlickExperiments2 {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
(Directorates.ddl ++ ServiceAreas.ddl).create
case class Directorate(dirCode: String, name: String) {
def serviceAreas: Seq[ServiceArea] = (for {
a <- ServiceAreas
if (a.dirCode === dirCode)
} yield (a)).list
}
object Directorates extends Table[Directorate]("DIRECTORATES") {
def dirCode = column[String]("DIRECTORATE_CODE", O.PrimaryKey)
def name = column[String]("NAME")
def * = dirCode ~ name <> (Directorate, Directorate.unapply _)
}
case class ServiceArea(areaCode: String, dirCode: String, name: String)
object ServiceAreas extends Table[ServiceArea]("SERVICE_AREAS") {
def areaCode = column[String]("AREAE_CODE", O.PrimaryKey)
def dirCode = column[String]("DIRECTORATE_CODE")
def name = column[String]("NAME")
def directorate = foreignKey("DIR_FK", dirCode, Directorates)(_.dirCode)
def * = areaCode ~ dirCode ~ name <> (ServiceArea, ServiceArea.unapply _)
}
Directorates.insert(Directorate("Dircode", "Dirname"))
ServiceAreas.insertAll(ServiceArea("a", "Dircode", "A"), ServiceArea("b", "Dircode", "B"))
val sa = (for{
d <- Directorates
} yield d).list map { case t: Directorate => t.serviceAreas}
println(sa)
}
//> List(List(ServiceArea(a,Dircode,A), ServiceArea(b,Dircode,B)))
}