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.
Related
I have a table with non nullable columns:
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def surname = column[String]("surname")
}
I want update some columns only (if not None):
def update(id: String, name: Option[String], surname: Option[String]) = {
(name, surname) match {
case (Some(n), Some(s)) => byId(id)
.map(l => (l.name, l.surname))
.update((n, s))
case (None, Some(s)) => byId(id)
.map(l => (l.surname))
.update(s)
case (Some(n),None) => byId(id)
.map(l => (l.name))
.update(n)
}
}
Is there more elegant way to do this? What if there are lot of update parameters?
Although I am able to make two queries, I am left with the option to use the existing one and always make only one update:
def byId(id: String) = ???
def update(id: String, name: Option[String], surname: Option[String]) = {
val filterById = byId(id).map(u => (u.name, u.surname))
for {
(existingName, existingSurname) <- filterById.result.head
rowsAffected <- filterById.update((name.getOrElse(existingName), surname.getOrElse(existingSurname)))
} yield rowsAffected
}
PD: Same for large objects .. we map the entire row and then make a kind of patch to update it again
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
I would like to add a case class SupplierID to my code. And I would like to simplify the * method by auto-Mapping SupplierID and Int Types.
case class SupplierID(id: Int)
case class Supplier(id: SupplierID, name: String, street: String, city: String, state: String, zip: String)
// Definition of the SUPPLIERS table using case class
class Suppliers(tag: Tag) extends Table[Supplier](tag, "SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey) // This is the primary key column
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
def * ={
(id, name, street, city, state, zip).shaped.<>({ tuple => Supplier.apply(SupplierID(tuple._1), tuple._2, tuple._3, tuple._4, tuple._5, tuple._6 )}, {
(s : Supplier) => Some{(s.id.id, s.name, s.street, s.city, s.state, s.zip)}
})
}
}
I'm trying to implement a column Type like this
// And a ColumnType that maps it to Int
implicit val SupplierIDColumnType = MappedColumnType.base[SupplierID, Int](
s => s.id, // map Bool to Int
i => SupplierID(i) // map Int to Bool
)
How to use such mappingtype ?
The solution:
case class SupplierID(id: Int)
// And a ColumnType that maps it to Int
implicit val SupplierIDColumnType = MappedColumnType.base[SupplierID, Int](
s => s.id, // map SupplierID to Int
i => SupplierID(i) // map Int to SupplierID
)
case class Supplier(id: SupplierID, name: String, street: String, city: String, state: String, zip: String)
// Definition of the SUPPLIERS table using case class
class Suppliers(tag: Tag) extends Table[Supplier](tag, "SUPPLIERS") {
def id = column[SupplierID]("SUP_ID", O.PrimaryKey) // This is the primary key column
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
def * = (id, name, street, city, state, zip) <>(Supplier.tupled , Supplier.unapply _)
}
Given the following Scala class enhanced with Slick:
class Users(tag: Tag) extends Table[(Int, String, String)](tag, "users") {
def id: Rep[Int] = column[Int]("sk", O.PrimaryKey)
def firstName: Rep[String] = column[String]("first_name")
def lastName: Rep[String] = column[String]("last_name")
def * : ProvenShape[(Int, String, String)] = (id, firstName, lastName)
}
I need to print the last names in a query loop:
val db = Database.forConfig("dbconfig")
try {
val users: TableQuery[Users] = TableQuery[Users]
val action = users.result
val future = db.run(action)
future onComplete {
case Success(u) => u.foreach { user => println("last name : " + **user.lastName**) }
case Failure(t) => println("An error has occured: " + t.getMessage)
}
} finally db.close
But Scala doesn't recognize user.lastName (I get an error saying that "Scala doesn't recognize the symbol"). How to print the last names ?
The problem is you're using Table[(Int, String, String)]. user in your case is therefore an instance of type (Int, String, String), so it doesn't have a lastName. Use user._3 to get at the tuple's third element (the last name). Even better might be to use a case class instead of a tuple:
case class DBUser(id: Int, firstName: String, lastName: String)
class Users(tag: Tag) extends Table[DBUser](tag, "users") {
def id: Rep[Int] = column[Int]("sk", O.PrimaryKey)
def firstName: Rep[String] = column[String]("first_name")
def lastName: Rep[String] = column[String]("last_name")
def * = (id, firstName, lastName) <> (DBUser.tupled, DBUser.unapply)
}
I Understand that nested classes work fine with Slick and we have that working in our product. We are facing an issue when the nested class is optional. For instance, look at the following code
class EmpTable(tag: Tag) extends Table[Emp](tag, "emp") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def aId = column[Option[Int]]("a_id")
def location = column[Option[String]]("address")
def address = (aId, location) <> (Address.tupled, Address.unapply)
def * = (id, name, address.?) <> (Emp.tupled, Emp.unapply)
}
case class Emp(id: Int, name: String, address: Option[Address])
case class Address(aId: Option[Int], location: Option[String])
This code does not compile because address does not have method ? .
Is there an easy way to get this working?
I got a solution for this:
class EmpTable(tag: Tag) extends Table[Emp](tag, "emp") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def aId = column[Option[Int]]("a_id")
def location = column[Option[String]]("address")
def * = (id, name, address, auth) <> (Emp.tupled, Emp.unapply)
def address = (aId, location).<>[Option[Address], (Option[Int], Option[String])]({ ad: (Option[Int], Option[String]) =>
ad match {
case (Some(id), Some(loc)) => Some(Address(id, loc))
case _ => None
}
}, {
adObj: Option[Address] =>
adObj match {
case add: Option[Address] => Some((add.map(_.aId), add.map(_.location)))
}
})