In Slick, how to provide default columns for Table - scala

I have some columns all my tables share, so I'd like to provide the default columns for all the tables. Following is what I have tried so far. I am using Slick 3.0.
// created_at and updated_at are the columns every table share
abstract class BaseTable[T](tag: Tag, tableName: String)
extends Table[T](tag, tableName) {
def currentWhenInserting = new Timestamp((new Date).getTime)
def createdAt = column[Timestamp]("created_at", O.Default(currentWhenInserting))
def updatedAt = column[Timestamp]("updated_at", O.Default(currentWhenInserting))
}
And the simple way to implement this seems like the following.
case class Student(
name: String, age: Int,
created_at: Timestamp, updated_at: Timestamp
)
class Students(tag: Tag) extends BaseTable[Student](tag, "students"){
def name = column[String]("name")
def age = column[Int]("age")
override def * : ProvenShape[Student] =
(name, age, createdAt, updatedAt).shaped <>
(Student.tupled, Student.unapply _)
}
But it's not desirable.
First, force every table row case class to incoporate created_at and updated_at. If I have more fields that would be totally unacceptable from the API design angle.
Second, write the two createdAt, updatedAt in (name, age, createdAt, updatedAt) explicitly. This is not what I expect about Default row.
My ideal way of solving this would be like the following:
case class Student(name: String, age: Int)
class Students(tag: Tag) extends BaseTable[Student](tag, "students"){
def name = column[String]("name")
def age = column[Int]("age")
override def * : ProvenShape[Student] =
(name, age).shaped <>
(Student.tupled, Student.unapply _)
}
That is, write some method in BaseTable or Define BaseCaseClass to avoid explicitly writing the extra two fields in table definition like Students and row case class definition Student.
However, after a painful struggle, still can get it done.
Any help would be greatly appreciated.

I'm using the following pattern:
case class Common(arg0: String, arg1: String)
trait Commons { this: Table[_] =>
def arg0 = column[String]("ARG0", O.Length(123))
def arg1 = column[String]("ARG1", O.Length(123))
def common = (arg0, arg1).<> [Meta, (String, String)](
r => {
val (arg0, arg1) = r
Meta(arg0, arg1)
},
o => Some(o.arg0, o.arg1)
)
}
case class MyRecord(a: String, b: String, common: Common)
class MyRecords(tag: Tag) extends Table[MyRecord](tag, "MY_RECORDS") with Commons {
def a = column[String]("A", O.PrimaryKey, O.Length(123))
def b = column[String]("B", O.Length(123))
def * = (a, b, common) <> (MyRecord.tupled, MyRecord.unapply)
}
It's not perfect but it helps avoid duplication without it being to difficult to understand.

Related

Slick column type of Any in Scala

I have the following question:
case class User(id: String, name: String, value: Any, userId: String)
and I want to create the table Users.
class Users(tag: Tag) extends Table[User](tag, "USERS"){
def id = column[String]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def value = column[Any]("VALUE")
def userId = column[String]("USER_ID")
// Every table needs a * projection with the same type as the table's type parameter
def * = (id.?, name, value, userId) <> (Fact.tupled, Fact.unapply _)
}
However I got an error:
could not find implicit value for parameter tt: slick.ast.TypedType[Any]
Not enought arguments for method column:(implicit slick.ast.TypedType[Any])slick.lifted.Rep[Any]
I think that I can not define Any as column type in slick or the case is different?

What's the best practice of simple Slick INSERT/GET?

I need to use Slick 3.1.1 for a Postgres based project, but I have a hard time to write clean code for the following super simple usage:
Assume I have a Task model:
case class Task(id: Option[UUID], foo: Int, bar: String)
The id: UUID is the primary key, so I should NOT provide it (id = None) when doing database INSERT. However, I do need it when doing GET which maps a database row to a Task object.
Therefore, the Slick Table class becomes very ugly:
class Tasks(tag: Tag) extends Table[Task](tag, "tasks") {
def id = column[UUID]("id", O.SqlType("UUID"), O.PrimaryKey)
def foo = column[Int]("foo")
def bar = column[String]("bar")
def insert: MappedProjection[Task, (Int, String)] =
(foo, bar).shaped.<>(
{ tuple =>
Task.tupled(None, tuple._1, tuple._2)
}, { (task: Task) =>
Task.unapply(task).map { tuple =>
(tuple._2, tuple._3)
}
}
)
override def * : ProvenShape[Task] =
(id.?,
foo,
bar).shaped.<>(Task.tupled, Task.unapply)
}
If case class Task has 10 elements, I then have to write (tuple._1, tuple._2, tuple._3, ......) My co-workers will slap my face if I submit a PR like above. Please suggest!
If you'll let the database to autoincrement your IDs, that Table definition could be shortened significantly:
import slick.driver.PostgresDriver.api._
import java.util.UUID
case class Task(id: Option[UUID], foo: Int, bar: String)
class Tasks(tag: Tag) extends Table[Task](tag, "tasks") {
def id = column[Option[UUID]]("id", O.SqlType("UUID"), O.PrimaryKey, O.AutoInc)
def foo = column[Int]("foo")
def bar = column[String]("bar")
def * = (id, foo, bar) <> (Task.tupled, Task.unapply)
}
This could be improved further by moving the id field to the end of the case class and giving it the default None value. This way you won't have to provide it every time you want to instantiate the Task:
case class Task(foo: Int, bar: String, id: Option[UUID] = None)
val firstTask = Task(123, "John")
val secondTask = Task(456, "Paul")

Reference compound primary key

I'm new to Slick and this is my first attempt of creating an application with it.
I have a case class User(userID: UUID, firstName: String, lastName: String).
Users can log in.
This is where case class LoginInfo(providerID: String, providerKey: String) comes in (I'm using Silhouette for authentication).
I'd like to associate every User with a LoginInfo using a Slick Table:
class UsersWithLoginInfos(tag:Tag) extends Table[(PrimaryKey, UUID)](tag, "USERS_WITH_LOGIN_INFOS"){
def loginInfoID = TableQuery[LoginInfos].shaped.value.loginInfoID
def userID = column[UUID]("USER_ID")
def * = (loginInfoID, userID) <>(UserWithLoginInfo.tupled, UserWithLoginInfo.unapply)
}
This is the corresponding case class UserWithLoginInfo(loginInfoID: PrimaryKey, userID: UUID).
My tables for Users and LoginInfos are straightforward:
class LoginInfos(tag: Tag) extends Table[LoginInfo](tag, "LOGIN_INFOS") {
// The primary key of this table is compound: it consists of the provider's ID and its key
def loginInfoID = primaryKey("LOGIN_INFO_ID", (providerID, providerKey))
// "credentials" for example
def providerID = column[String]("PROVIDER_ID")
// "admin#nowhere.com" for example
def providerKey = column[String]("PROVIDER_KEY")
def * = (providerID, providerKey) <>(LoginInfo.tupled, LoginInfo.unapply)
}
class Users(tag: Tag) extends Table[User](tag, "USERS") {
def id = column[UUID]("ID", O.PrimaryKey)
def firstName = column[String]("FIRST_NAME")
def lastName = column[String]("LAST_NAME")
def * = (id, firstName, lastName) <>(User.tupled, User.unapply)
}
Unfortunately, this doesn't typecheck:
def * = (loginInfoID, userID) <>(UserWithLoginInfo.tupled, UserWithLoginInfo.unapply)
Expression of type MappedProjection[UserWithLoginInfo, (PrimaryKey,
UUID)] doesn't conform to expected type ProvenShape[(PrimaryKey,
UUID)]
I could work around this by introducing a case class LoginInfoWithID(info: LoginInfo, id: UUID) but I hope to get away with referencing LoginInfo's compound primary key directly.
Is there a way to do this? Or am I on the wrong track entirely?
I'm using Slick 3.0.0.
I'm not quite sure what you are trying to achieve here, but if it is just to get an User with its associated LoginfInfo (untested code ahead):
class LoginInfos(tag: Tag) extends Table[LoginInfo](tag, "LOGIN_INFOS") {
def providerID = column[String]("PROVIDER_ID")
def providerKey = column[String]("PROVIDER_KEY")
def * = (providerID, providerKey) <>(LoginInfo.tupled, LoginInfo.unapply)
def pk = primaryKey("PK_LOGIN_INFO_ID", (providerID, providerKey))
}
class Users(tag: Tag) extends Table[User](tag, "USERS") {
def id = column[UUID]("ID", O.PrimaryKey)
def firstName = column[String]("FIRST_NAME")
def lastName = column[String]("LAST_NAME")
def * = (id, firstName, lastName) <>(User.tupled, User.unapply)
}
class UsersWithLoginInfos(tag:Tag) extends Table[(providerID: String, providerKey: String, UUID: String)](tag, "USERS_WITH_LOGIN_INFOS"){
def providerID = column[String]("USER_PROVIDER_ID") // column name assumption
def providerKey = column[String]("USER_PROVIDER_KEY") // column name assumption
def userID = column[UUID]("USER_ID")
def pk = primaryKey("PK_USER_WITH_LOGIN_INFO_ID", (providerID, providerKey))
}
case class UserWithLoginInfo(user: User, loginInfo: LoginInfo)
You can optionally wrap the Tuple3 in UsersWithLoginInfosin a case class if you like to. However, this is an example query to get an User joined with its LoginInfo:
def findUserWithLoginInfo(providerID: String, providerKey: String): Future[Option[UserWithLoginInfo]] = {
val query = (for {
user <- TableQuery[Users] if user.providerID === providerID && user.providerKey === providerKey
userToLoginInfo <- TableQuery[UsersWithLoginInfos] if userToLoginInfo.userID === user.id
loginInfo <- TableQuery[LoginInfos] if userToLoginInfo.providerID === loginInfo.providerID && userToLoginInfo.providerID === loginInfo.providerKey
} yield UserWithLoginInfo(user, loginInfo))
db.run(query.result.headOption)
}
Instead of writing a rather verbose query like above, you can also define a foreign key constraint as it is described here Slick documentation, Constraints.
Also I found Coming from ORM to Slick very useful when I started using slick. Remember, slick is NOT an ORM and thus it does things quite different :)

Scala Slick single filter for multiple TableQuery instances

I'm using Scala Slick-3.1.0 lib.
How is it possible to make a generic Slick filter function that takes TableQuery instance as an input and makes same slick filter on it?
I have several case classes (two for example) representing data stored in DB.
Some fields are the same so classes could possibly extend common ancestor:
case class FirstData(symbol: String,
date: Timestamp,
firstField: Double)
case class SecondData(symbol: String,
date: Timestamp,
secondField: String)
Each of them has its own SQL Table in DB and is represented by separate Slick Table class. Also they have same primary keys:
class FirstDataTable(tag: Tag) extends Table[FirstData](tag, "firstData") {
def symbol = column[String]("symbol")
def date = column[Timestamp]("date")
def firstField= column[Double]("firstField")
def * = (symbol, date, firstField) <> ((FirstData.apply _).tupled, FirstData.unapply)
def pk = primaryKey("pk_firstData", (symbol, date))
}
class SecondDataTable(tag: Tag) extends Table[SecondData](tag, "secondData"){
def symbol = column[String]("symbol")
def date = column[Timestamp]("date")
def secondField= column[String]("secondField")
def * = (symbol, date, secondField) <> ((SecondData.apply _).tupled, SecondData.unapply)
def pk = primaryKey("pk_secondData", (symbol, date))
}
Finally TableQuery classes are:
val firstDataTableQuery = TableQuery[FirstDataTable]
val secondDataTableQuery = TableQuery[SecondDataTable]
etc ...
How is it possible to make a generic Slick filter query function that takes firstDataTableQuery or secondDataTableQuery as an argument and makes same slick query on input. Filtering only on their common fields or another way saying on their SQL Table representations common columns. For example like this:
def filter(genericTableQuery: TableQuery) = {
genericTableQuery.filter { data => dataFilterFunction(data.symbol)
}.filter(_.date >= someDate).sortBy(data => data.date.asc)
}
val firstTableResult = filter(firstDataTableQuery)
val seconfTableResult = filter(secondDataTableQuery)
etc ...
I looked on this topics, but still could not make up a solution:
Slick 3 reusable generic repository
Scala reflection to instantiate scala.slick.lifted.TableQuery
How about making the data table classes extend a common trait, which in turn extends a common trait for data classes, like for example:
trait Data {
def symbol: String
def date: Timestamp
}
// make FirstData and SecondData extend the above trait
trait GenericDataTable extends Data {
def symbol: Rep[String]
def date: Rep[Timestamp]
def pk: PrimaryKey
}
class FirstDataTable(tag: Tag) extends Table[FirstData](tag, "firstData") with GenericDataTable {
// ...
and then:
def filter[T <: GenericDataTable](genericTableQuery: TableQuery[T]) = // ...
invariant, thanks a lot. You pointed me to the right direction. I slightly modified your answer and everything works )
Traits:
trait Data {
def symbol: String
def date: Timestamp
}
trait GenericDataTable[T <: Data] extends Table[T] {
def symbol: Rep[String]
def date: Rep[Timestamp]
def pk: PrimaryKey
}
FirstData and FirstDataTable classes look like this:
case class FirstData(
symbol: String,
date: Timestamp,
firstField: Double) extends Data
class FirstDataTable(tag: Tag) extends Table[FirstData(tag,"firstData")
with GenericDataTable[FirstData] {
def symbol = column[String]("symbol")
def date = column[Timestamp]("date")
def firstField= column[Double]("firstField")
def * = (symbol, date, firstField) <> ((FirstData.apply _).tupled, FirstData.unapply)
def pk = primaryKey("pk_firstData", (symbol, date))
}
Finally function looks like this:
private def filter[M <: Data, T <: GenericDataTable[M]] (genericTableQuery: TableQuery[T]) = {
genericTableQuery.filter { data => dataFilterFunction(data.symbol)}.
filter(_.date >= someDate).sortBy(data => data.date.asc)
}

Extending SLICK Tables in a DRY manner

I have an interesting question around Slick/Scala that I am hoping that one of you nice chaps might be able to assist me with.
I have several tables and by extension in SLICK case classes
case class A(...)
case class B(...)
case class C(...)
that share these common fields
(id: String, livemode: Boolean, created: DateTime, createdBy : Option[Account]) .
Because these fields are repeated in every case class, I'd like to explore the possibility of extracting them into a single object or type.
However, when creating the SLICK table objects I would like things to end up where these common fields are included too so I can persist their individual values in each table.
object AsTable extends Table[A]("a_table") {
...
def id = column[String]("id", O.PrimaryKey)
def livemode = column[Boolean]("livemode", O.NotNull)
def created = column[DateTime]("created", O.NotNull)
def createdBy = column[Account]("created_by", O.NotNull)
...
}
Effectively, the end result I'm looking for is to allow me make changes to the common fields without having to update each table.
Is there a way to do this?
Thanks in advance
I have not tried this, but how about a trait you mix in:
trait CommonFields { this: Table[_] =>
def id = column[String]("id", O.PrimaryKey)
def livemode = column[Boolean]("livemode", O.NotNull)
def created = column[DateTime]("created", O.NotNull)
def createdBy = column[Account]("created_by", O.NotNull)
protected common_* = id ~ livemode ~ created ~ createdBy
}
Then you can do:
object AsTable extends Table[(String,Boolean,DateTime,Account,String)]("a_table")
with CommonFields {
def foo = column[String]("foo", O.NotNull)
def * = common_* ~ foo
}
The only thing you'll have to repeat now is the type of the elements.
UPDATE
If you want to do object-mapping and:
You map to case-classes
The fields in your case classes are in the same order
Just do:
case class A(
id: String,
livemode: Boolean,
created: DateTime,
createdBy: Account,
foo: String)
object AsTable extends Table[A]("a_table") with CommonFields {
def foo = column[String]("foo", O.NotNull)
def * = common_* ~ foo <> (A.apply _, A.unapply _)
}
This seems to be the most economical solution (rather then trying to define * in CommonFields and adding a type parameter). However, it requires you to change all case classes if your fields change.
We could try to mitigate this by using composition on the case classes:
case class Common(
id: String,
livemode: Boolean,
created: DateTime,
createdBy: Account)
case class A(
common: Common,
foo: String)
However, when constructing the mapper function, we will (somewhere) end up having to convert tuples of the form:
(CT_1, CT_2, ... CT_N, ST_1, ST_2, ..., ST_M)
CT Common type (known in CommonFields)
ST Specific type (known in AsTable)
To:
(CT_1, CT_2, ... CT_N), (ST_1, ST_2, ..., ST_M)
In order to pass them to subroutines individually converting Common and A to and from their tuples.
We have to do this without knowing the number or the exact types of either CT (when implementing in AsTable) or ST (when implementing in CommonFields). The tuples in the Scala standard library are unable to do that. You would need to use HLists as for exampled offered by shapeless to do this.
It is questionable whether this is worth the effort.
The basic outline could look like this (without all the implicit mess which will be required). This code will not compile like this.
trait CommonFields { this: Table[_] =>
// like before
type ElList = String :: Boolean :: DateTime :: Account :: HNil
protected def toCommon(els: ElList) = Common.apply.tupled(els.tupled)
protected def fromCommon(c: Common) = HList(Common.unapply(c))
}
object AsTable extends Table[A] with CommonFields {
def foo = column[String]("foo", O.NotNull)
def * = common_* ~ foo <> (x => toA(HList(x)), x => fromA(x) tupled)
// convert HList to A
protected def toA[L <: HList](els: L) = {
// Values for Common
val c_els = els.take[Length[ElList]]
// Values for A
val a_els = toCommon(c_els) :: els.drop[Length[ElList]]
A.apply.tupled(a_els.tupled)
}
// convert A to HList
protected def fromA(a: A) =
fromCommon(a.common) :: HList(A.unapply(a)).drop[One]
}
Using some more type magic you can probably resolve the last two issues:
Put toA and fromA into the base trait (by using type parameters in the trait, or using abstract type members)
Avoid defining ElList explicitly by extracting it from Common.apply by using this technique