I have 2 case classes
case class PPU(id : String,
name : String,
mini_ppus : List[MiniPPU]..)
case class MiniPPU( minPpuId : String,
ppu_id : String.. )
I have a PPU table and want to store only id and name but not the mini_ppus.
class PpuTable(tag: Tag) extends Table[PPU](tag, None, _tableName ="ppu") {
def * = (id, name) <> (
{ tuple: (String,String) =>
PPU(id.asInstanceOf[String], name.asInstanceOf[String], ListBuffer[MiniPPU]().toList)},
{ ppuTable: PPU => Option(ppuTable.id, ppuTable.name) }
)
val id = column[String]("ppu_id", O.PrimaryKey)
val name: Rep[String] = column[String]("name")
}
Whenever im trying to select something from the table, i'm getting an error:
java.lang.ClassCastException: slick.relational.RelationalTableComponent$Table$$anon$1 cannot be cast to java.lang.String
what is the correct way to override def * method ?
Instead of this:
{ tuple: (String,String) => PPU(id.asInstanceOf[String], name.asInstanceOf[String], ListBuffer[MiniPPU]().toList)}
try this:
{ tuple: (String, String) => PPU(tuple._1, tuple._2, ListBuffer[MiniPPU]().toList)}
Use the values from the tuple, not the ones declared before <>
Related
In slick you write a projection that:
defines how the columns are converted to and from the Person object.
the default format is:
def * = (id, name, age) <> ((Person.apply _).tupled, Person.unapply)
We can use it to directly map classes/tuples to database tables.
Can you use it to alter values as part of the conversion?
E.g. purely as an example, could you set a constant value in the Person object, but ignore it in the database? Or map the name as a String in the database, and as an enum in the Person object?
The following code compiles
import slick.jdbc.PostgresProfile.api._
trait Name
object Name {
case class Ordinary(s: String) extends Name
case class Manager(s: String) extends Name
case object NoName extends Name
}
case class Person(id: Long, name: Name, age: Int, isValid: Boolean)
class Persons(tag: Tag) extends Table[Person](tag, "persons") {
def id = column[Long]("id", O.PrimaryKey)
def name = column[String]("name")
def age = column[Int]("age")
def * = (id, name, age) <> ({
case (l, "NoName", i) => Person(l, Name.NoName, i, true)
case (l, s, i) if s.startsWith("Mgr.") => Person(l, Name.Manager(s.stripPrefix("Mgr.")), i, true)
case (l, s, i) => Person(l, Name.Ordinary(s), i, true)
}, (p: Person) => p.name match {
case Name.Ordinary(s) => Some((p.id, s, p.age))
case Name.Manager(s) => Some((p.id, "Mgr." + s, p.age))
case Name.NoName => Some((p.id, "NoName", p.age))
})
}
Here we set isValid to be a constant value and map name to enum.
class MyTable(tag: Tag) extends Table[MyEntity](tag, "1970Table") {
def id = column[Int]("id")
override def * =
(
id
) <> (MyEntity.tupled, MyEntity.unapply)
}
val myTable = TableQuery[MyTable]
class MyRepository(val config: DatabaseConfig[JdbcProfile])
extends MyRepository[MyTable, String] {
override val table: config.profile.api.TableQuery[MyTable] = myTable
def insert(me: MyEntity): Future[Int] = {
db.run(table += me)
}
}
I use this in my other classes like this:
val myRepository = new MyRepository(dbConfig)
myRepository.insert(myrecord)
Question
I would like to not have a hardcoded tablename but rather make the tablename dynamic.
I would like to change the insert method such that it accepts a year (int) parameter and based on the year parameter it chooses the right table. i.e. if the year passed in is 1970 then table name is 1970Table but if the year passed in is 1980 then the table is 1980Table.
Try
class MyRepository(val config: DatabaseConfig[JdbcProfile]) {
import config._
import profile.api._
abstract class MyTable(tag: Tag, name: String) extends Table[MyEntity](tag, name) {
def id = column[Int]("id")
override def * = (id) <> (MyEntity.tupled, MyEntity.unapply)
}
class Table1970(tag: Tag) extends MyTable[MyEntity](tag, "1970Table")
class Table1980(tag: Tag) extends MyTable[MyEntity](tag, "1980Table")
val table1970 = TableQuery[Table1970]
val table1980 = TableQuery[Table1980]
def insert(me: MyEntity, year: Int): Future[Int] = db.run {
year match {
case "1970" => table1970 += me
case "1980" => table1980 += me
}
}
}
Now
val myRepository = new MyRepository(dbConfig)
myRepository.insert(myrecord, "1970")
There is two apply methods in TableQuery. val myTable = TableQuery[MyTable] -
this one uses macros to create MyTable.
The other one is defined like this:
def apply[E <: AbstractTable[_]](cons: Tag => E): TableQuery[E] =
new TableQuery[E](cons)
So you can do smth like this
class MyTable(tag: Tag, tableName: String) extends Table[MyEntity](tag, tableName)
...
def myTable(name: String) = TableQuery[MyTable](tag => new MyTable(tag, name))
Now you can predefine all tables you need and use them or do smth like this
class MyRepository(val config: DatabaseConfig[JdbcProfile])
extends MyRepository[MyTable, String] {
override def table(year: Int): config.profile.api.TableQuery[MyTable] = myTable(year.toString)
def insert(me: MyEntity, year: Int): Future[Int] = {
db.run(table(year) += me)
}
}
Could you please explain to me how I can convert MappedProjection to ProvenShape which currently fails due to ambiguous implicit?
I use slick-pg for support of jsonb types in Postgres DB.
I have simple case class that I want to store as json and there is ID column with value from case class.
case class Topic(id: String, title: String)
class TopicRow(tag: Tag) extends Table[(String, Topic)](tag, Topic.tableName) {
def id = column[String]("id", O.PrimaryKey)
def json = column[Topic]("json")
def * = (id, json)
}
earlier in the code I have conversion between json and case class
import spray.json._
implicit val TopicColumnType = MappedColumnType.base[Topic, JsValue ](
{ obj => obj.toJson }, { json => json.convertTo[Topic] }
)
What I don't like about TopicRow is that it maps to tuple. I'd like it to be something like this
class TopicRow(tag: Tag) extends Table[Topic](tag, Topic.tableName) {
def id = column[String]("id", O.PrimaryKey)
def json = column[Topic]("json")
val tupled: ((String, Topic)) => (Topic) = tu => tu._2
val unapply: (Topic) => Option[(String, Topic)] = t => Option((t.id, t))
def * = (id, json) <> (tupled, unapply)
}
It fails to compile with error
Error:(43, 22) type mismatch;
found : slick.lifted.MappedProjection[co.younetworks.medstream.server.models.db.Topic,(String, co.younetworks.medstream.server.models.db.Topic)]
required: slick.lifted.ProvenShape[co.younetworks.medstream.server.models.db.Topic]
So I specified explicitly every step of conversion like this
def * = {
val shape: ProvenShape[Topic] = {
val tupled: ((String, Topic)) => (Topic) = tu => tu._2
val unapply: (Topic) => Option[(String, Topic)] = t => Option((t.id, t))
val toToShapedValue: ToShapedValue[(Rep[String], Rep[Topic])] = anyToToShapedValue((id, json))
val mappedProjection: MappedProjection[Topic, (String, Topic)] = toToShapedValue <>(tupled, unapply)
ProvenShape.proveShapeOf(mappedProjection)
}
shape
}
that gives me error
Error:(52, 31) ambiguous implicit values:
both method mappedProjectionShape in object MappedProjection of type [Level >: slick.lifted.FlatShapeLevel <: slick.lifted.ShapeLevel, T, P]=> slick.lifted.Shape[Level,slick.lifted.MappedProjection[T,P],T,slick.lifted.MappedProjection[T,P]]
and method repColumnShape in trait RepShapeImplicits of type [T, Level <: slick.lifted.ShapeLevel](implicit evidence$1: slick.ast.BaseTypedType[T])slick.lifted.Shape[Level,slick.lifted.Rep[T],T,slick.lifted.Rep[T]]
match expected type slick.lifted.Shape[_ <: slick.lifted.FlatShapeLevel, slick.lifted.MappedProjection[co.younetworks.medstream.server.models.db.Topic,(String, co.younetworks.medstream.server.models.db.Topic)], U, _]
ProvenShape.proveShapeOf(mappedProjection)
^
I suspect I confused compiler with presence of mapped column but I don't know how to fix it.
Well, explicit specification of implicit helped. It doesn't look pretty though.
class TopicRow(tag: Tag) extends Table[Topic](tag, Topic.tableName) with JsonMarshallers {
def id = column[String]("id", O.PrimaryKey)
def json = column[Topic]("json")
private val fromTuple : ((String, Topic)) => (Topic) = { case (_, value) => value }
private val toTuple = (v : Topic) => Option((v.id, v))
def * = ProvenShape.proveShapeOf((id, json) <> (fromTuple, toTuple))(MappedProjection.mappedProjectionShape)
}
If anyone knows how to make it shorted please let me know.
I'd like to insert a new record that contains a foreign key.
For example, inserting the new record to bars, and the new record has the id that is found by the query result of the table foos.
Here is a code example:
import scala.slick.driver.H2Driver.simple._
object Test {
case class FooRecord(id:Int, str: String)
class Foos(tag: Tag) extends Table[FooRecord](tag, "users") {
def id = column[Int]("ID", O.PrimaryKey)
def str = column[String]("EMAIL", O.NotNull)
def * = (id, str) <> (FooRecord.tupled, FooRecord.unapply _)
}
val foos = TableQuery[Foos]
case class BarRecord(app_id:Int, name: String)
class Bars(tag: Tag) extends Table[BarRecord](tag, "apps") {
def foo_id = column[Int]("FOO_ID")
def str = column[String]("STR", O.NotNull)
def * = (foo_id, str) <> (BarRecord.tupled, BarRecord.unapply _)
def foo_fk = foreignKey("FOO_FK", foo_id, foos)(_.id)
}
val bars = TableQuery[Bars]
def main(args: Array[String]): Unit = {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
implicit session =>
foos.filter(_.str === "ABC").map { f =>
// Insert a new record that contains foo's id as a foreign key to bars.
// bars.insert(BarRecord(f.id, "DEF"))
// [error] found : scala.slick.lifted.Column[Int]
// [error] required: Int
bars.insert(BarRecord(1, "DEF")) // OK
}
}
}
}
I got a compile error. The type of the foreign key is Column[Int], but the BarRecord id type is Int.
Is there any good way to get Int value? Or is there a more elegant way that can insert the value that is from another table's query result?
Your query returns the lifted query, not the Int value:
val fooIdColumn: lifted.Query[lifted.Column[Int], Int] =
foos.filter(_.str === "ABC").map(f => f.id)
You can use run and get back a Seq[Int] (since there could be more than one result):
val fooIds: Seq[Int] =
foos.filter(_.str === "ABC").map(x => x.id).run
Or firstOption and getOrElse to get back an Int:
val fooId: Int =
foos.filter(_.str === "ABC").map(x => x.id).firstOption.getOrElse(0)
Assume we have a table
class Entry(tag :Tag) extends Table[(String, Long)](tag, "entries") {
def name = column[String]("name")
def value = column[Long]("value")
def * = (name, value)
}
val Entries = new TableQuery(new Entry(_))
and a query of type Query[(Column[String], Column[Long]), (String, Long)]. Can I somehow convert it to Query[Entry, (String, Long)]? This would be very useful in case of grouping queries such as Entries.groupBy(_.name).map(g=>(g._1, g._2.map(_.value).avg))
try this :
case class Entry(name: String,value: Long)
class Entries(tag :Tag) extends Table[Entry](tag, "entries") {
def name = column[String]("name")
def value = column[Long]("value")
def * = (name, value) <>(Entry.tupled, Entry.unapply )
}
val Entries = TableQuery[Entries]