I'm using Slick to create an application which stores a bunch of records about songs in an Hsqldb database.
Currently my tables are defined as:
abstract class DBEnum extends Enumeration {
def enum2StringMapper(enum: Enumeration) = MappedJdbcType.base[enum.Value, String](
b => b.toString,
i => enum.withName(i))
}
class Artist(tag: Tag) extends Table[(Int, String)](tag, "ARTIST") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME", O.NotNull)
def nameIndex = index("NAME_IDX", name, unique = true)
def * = (id, name)
}
class Song(tag: Tag) extends Table[(Int, String, Int)](tag, "SONG") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME", O.NotNull)
def artistId = column[Int]("ARTIST_ID")
def artistFk = foreignKey("ARTIST_FK", artistId, TableQuery[Artist])(_.id)
def idNameIndex = index("ID_NAME_IDX", (id, name), unique = true)
def * = (id, name, artistId)
}
object BroadcastType extends DBEnum {
implicit val BroadcastTypeMapper = enum2StringMapper(BroadcastType)
type BroadcastType = Value
val PLAYED = Value("Played")
val NOW = Value("Now")
val NEXT = Value("Next")
}
class Broadcast(tag: Tag) extends Table[(Int, Timestamp, BroadcastType.BroadcastType)](tag, "BROADCAST") {
def songId = column[Int]("SONG_ID")
def dateTime = column[Timestamp]("DATE_TIME")
def broadcastType = column[BroadcastType.BroadcastType]("BROADCAST_TYPE")
def pk = primaryKey("BROADCAST_PK", (songId, dateTime))
def songFk = foreignKey("SONG_FK", songId, TableQuery[Song])(_.id)
def * = (songId, dateTime, broadcastType)
}
I'm still just setting things up so not sure if it's correct but hopefully you get the idea.
Now what I want to do is keep my composite primary key on the Broadcast table but I want to create a clustered index on the timestamp. Most of my queries on that table will be filtered by ranges on the timestamp. Rows will be inserted with an increasing timestamp so there is minimal shuffling of records to maintain the physical order.
Is there any abstraction to create a clustered index in Slick? So far it seems like I'm going to have to fall back to using plain SQL.
Yes, you'll have to use plain sql now. You can create a feature request or a PR: https://github.com/slick/slick/issues
Related
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 :)
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.
I am having a looking at Slick 2.0
In this tutorial, the schema is as below:
// Definition of the SUPPLIERS table
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](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")
// Every table needs a * projection with the same type as the table's type parameter
def * = (id, name, street, city, state, zip)
}
val suppliers = TableQuery[Suppliers] //Definition TableQuery for suppliers
// Definition of the COFFEES table
class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
def name = column[String]("COF_NAME", O.PrimaryKey)
def supID = column[Int]("SUP_ID")
def price = column[Double]("PRICE")
def sales = column[Int]("SALES")
def total = column[Int]("TOTAL")
def * = (name, supID, price, sales, total)
// A reified foreign key relation that can be navigated to create a join
def supplier = foreignKey("SUP_FK", supID, suppliers)(_.id) // Supplier defined above
}
val coffees = TableQuery[Coffees]
Here, the definition of the TableQuery[Suppliers] is done in the same file as the definition of Coffee so we can supply a TableQuery for the foreignKey (supplier)
Now, I would like to keep each class in a file and create the TableQuery whereever I need .
My question is:
How should I go to define the foreign key in the Coffee class and keep in a seperate file the Suppliers class?
Do I have to create those TableQuery in an Scala object and import it the Suppliers class so that I can provide a TableQuery to the foreignKey definition ?
I hope I was clear enough :/
Thank you
You need to simply reference the TableQuery to which the foreign key relates to:
// SuppliersSchema.scala
object SuppliersSchema {
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") {
/* Omitted for brevity */
}
val suppliers = TableQuery[Suppliers] //Definition TableQuery for suppliers
}
// CoffeesSchema.scala
object CoffeesSchema {
class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
/* Omitted for brevity */
def supplier = foreignKey("SUP_FK", supID, SuppliersSchema.suppliers)(_.id) // define in another file
}
val coffees = TableQuery[Coffees]
}
Another way would be to create a TableQuery reference to Suppliers inside of CoffeesSchema and use that in your foreign key definition key, anyway this approach is untested as I personally prefer the first one.
The following code shows a module (in the form of a trait) containing two Slick table definitions, with the second having a fk reference to the first. Each table object defines an inner case class called Id, which is used as its primary key. This all compiles and works just fine.
trait SlickModule {
val driver = slick.driver.BasicDriver
import driver.Table
case class A(id: TableA.Id, name: String)
case class B(id: TableB.Id, aId: TableA.Id)
import scala.slick.lifted.MappedTypeMapper
implicit val aIdType = MappedTypeMapper.base[TableA.Id, Long](_.id, new TableA.Id(_))
implicit val bIdType = MappedTypeMapper.base[TableB.Id, Long](_.id, new TableB.Id(_))
object TableA extends Table[A]("table_a") {
case class Id(id: Long)
def id = column[TableA.Id]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.NotNull)
def * = id ~ name <> (A.apply _, A.unapply _)
}
object TableB extends Table[B]("table_b") {
case class Id(id: Long)
def id = column[Id]("id", O.PrimaryKey, O.AutoInc)
def aId = column[TableA.Id]("fk_a", O.NotNull)
def fkA = foreignKey("fk_a", aId, TableA)(_.id)
def * = id ~ aId <> (B.apply _, B.unapply _)
}
}
However, if I change the column definition of id in TableA from
def id = column[TableA.Id]("id", O.PrimaryKey, O.AutoInc)
to this, by removing the explicit path to Id
def id = column[Id]("id", O.PrimaryKey, O.AutoInc)
I get the following compilation error:
type mismatch;
found : SlickModule.this.TableA.type => scala.slick.lifted.Column[x$5.Id] forSome { val x$5: SlickModule.this.TableA.type }
required: SlickModule.this.TableA.type => scala.slick.lifted.Column[SlickModule.this.TableA.Id]
Error occurred in an application involving default arguments.
def fkA = foreignKey("fk_a", aId, TableA)(_.id)
^
So the type parameter of the aId column is found along a path that now includes TableA.type, whilst the parameter is just expected to be TableA.Id. Can anyone explain why this difference occurs and how I might get around it without needing the explicit reference to the TableA object? I am trying to abstract out the definition of my primary key columns into a trait, and this problem is preventing me from doing that.
I am using the Scala 2.10.2 compiler.
I am not completely sure why exactly your code gets a compilation error, but the following seems to achieve your goals:
trait TableModule {
import scala.slick.lifted.{MappedTypeMapper, BaseTypeMapper}
val driver = slick.driver.BasicDriver
case class Id(id: Long)
type Row
abstract class Table(name: String) extends driver.Table[Row](name) {
def id = column[Id]("id", O.PrimaryKey, O.AutoInc)
import driver.Implicit._
def findById(id: Id) = (for (e <- this if (e.id === id)) yield e)
}
implicit def idTypeMapper : BaseTypeMapper[Id] = MappedTypeMapper.base[Id, Long](_.id, new Id(_))
}
trait Schema {
object ModuleA extends TableModule {
case class Row(id: Id, name: String)
object table extends Table("table_a") {
def name = column[String]("name", O.NotNull)
def * = id ~ name <> (Row.apply _, Row.unapply _)
}
}
object ModuleB extends TableModule {
case class Row(id: Id, aId: ModuleA.Id)
object table extends Table("table_b") {
def name = column[String]("name", O.NotNull)
def aId = column[ ModuleA.Id]("fk_a", O.NotNull)
def fkA = foreignKey("fk_a", aId, ModuleA.table)(_.id)
def * = id ~ aId <> (Row.apply _, Row.unapply _)
}
}
}
object schema extends Schema {
def main(args: Array[String]): Unit = {
val ddl = ModuleA.table.ddl ++ ModuleB.table.ddl
println("Create:")
ddl.createStatements.foreach(println)
println("Delete:")
ddl.dropStatements.foreach(println)
}
}
In particular the Id classes associated with different tables are distinct so that
val aid = ModuleA.Id(1)
val bid : ModuleB.Id = aid
fails to compile with
[error] found : Schema.ModuleA.Id
[error] required: Schema.ModuleB.Id
[error] val bid : ModuleB.Id = aid
How can I return a mapped object using Slick? Using the following code my query returns List[(Int, String)] and not a List[Task] like I want it to. Is this not possible using Slick or am I thinking about Slick the wrong way is it not an ORM? I'm trying to return a query and use it in a view template using the Play2 framework. I'd like to end up accessing the objects like task.id task.label etc... Thanks.
import play.api.Play.current
import play.api.db._
import scala.slick.driver.H2Driver.simple._
case class Task(id: Int, label: String)
object Task extends Table[(Int, String)]("TASKS") {
lazy val database = Database.forDataSource(DB.getDataSource())
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def label = column[String]("LABEL")
def * = id ~ label
def all() : List[Task] = database.withSession { implicit db: Session =>
Query(Task).list
}
}
The issue is with how you defined your table. Try changing your table definition to:
case class Task(id: Int, label: String)
object Task extends Table[Task]("TASKS") {
lazy val database = Database.forDataSource(DB.getDataSource())
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def label = column[String]("LABEL")
def * = id ~ label <> (Task.apply _, Task.unapply _)
def all() : List[Task] = database.withSession { implicit db: Session =>
Query(Task).list
}
}
The difference is that the type param I am passing to the Table is Task instead of (Int, String). This should fix your issue.