Reference compound primary key - scala

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 :)

Related

Sangria: How to handle custom types

trying work with Sangria and Slick. New to both of them.
I have bunch of tables which share a list of common fields. Slick's representation of this is below:
case class CommonFields(created_by: Int = 0, is_deleted: Boolean = false)
trait CommonModel {
def commonFields: CommonFields
def created_by = commonFields.created_by
def is_deleted = commonFields.is_deleted
}
case class User(id: Int,
name: String,
commonFields: CommonFields = CommonFields()) extends CommonModel
Slick tables:
abstract class CommonTable [Model <: CommonModel] (tag: Tag, tableName: String) extends Table[Model](tag, tableName) {
def created_by = column[Int]("created_by")
def is_deleted = column[Boolean]("is_deleted")
}
case class CommonColumns(created_by: Rep[Int], is_deleted: Rep[Boolean])
implicit object CommonShape extends CaseClassShape(
CommonColumns.tupled, CommonFields.tupled
)
class UsersTable(tag: Tag) extends CommonTable[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def * = (id,
name,
CommonColumns(created_by, is_deleted)) <> (User.tupled, User.unapply)
}
val Users = TableQuery[UsersTable]
The problem is with Graphql:
lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User]()
When I try to create UserType using derivedObjectType Macro, it complains that
Can't find suitable GraphQL output type for .CommonFields. If you have defined it already, please consider making it implicit and ensure that it's available in the scope.
[error] lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User](
How do I tell Sangria/Graphql how to handle this nested list of fields (from CommonFields) ?
Please help.
You are deriving the Type for User but user also has CommonFields which you have not derived. So it's not able to find Type information for CommonFields. Derive both and make the derivation implicit for CommonFields.
implicit val CommonFieldsType: ObjectType[Unit, CommonFields] = deriveObjectType[Unit, CommonFields]
implicit val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User]

Slick using mapped column type in update statement

I have a trouble in using slick MappedColumnType, the code snippet is as following:
private trait UserTable {
self: HasDatabaseConfigProvider[JdbcProfile] =>
import driver.api._
lazy val userTable = TableQuery[User]
class UserTable(tag: Tag)
extends Table[User](tag, "user") {
implicit def mapper = MappedColumnType.base[JsObject, String](
{ scope: JsObject => scope.toString }, { s: String => Json.parse(s).as[JsObject] }
)
val id = column[Long]("id", O.PrimaryKey, O.AutoInc)
val name = column[String]("name")
val hobby = column[JsObject]("hobby")
def * = (id, name, hobby) <> (User.tupled, User.unapply)
}
}
My User case class is defined as follow:
case class User(id: Long, name: String: hobby: JsObject)
I have corresponding insert and update statement for my database. However the following update statement is not working for me.
def updateQuery(id: Long, newUser: User) = {
userTable.filter(x => x.id === id)
.map(x => (x.hobby))
.update(newUser.hobby)
It will throw a compile error:
No matching Shape found.
Slick does not know how to map the given types.
Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List).
I think it's pretty straight-forward. Is there something I did wrong?

In Slick, how to provide default columns for Table

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.

How to use type mappers with Slick Multi-DB Pattern?

I'm using Slick 3.0 and following the Slick Multi-DB Pattern so that the actual DB driver is abstracted. I use several type mappings which are defined in a single object TypeMappers. Now I want to abstract these type mappings from the particular DB driver too. This is why I moved TypeMappers into a dedicated trait. I believe this is the right approach, but I'm struggling how to import TypeMappers so that the implicits are visible for class User. Any help would be great.
trait TypeMappersTrait { this: Driver =>
import driver.api._
object TypeMappers {
implicit val JavaUtilDateTypeMapper = MappedColumnType.base[java.util.Date, Long](_.getTime, new java.util.Date(_))
implicit val URLMapper = MappedColumnType.base[URL, String](_.toString, new URL(_))
implicit val WrappedByteArrayTypeMapper = MappedColumnType.base[WrappedArray[Byte], Array[Byte]](_.toArray, wrapByteArray(_))
}
}
/** A User contains a name, picture and ID */
case class User(name: String, picture: Picture, id: Option[Int] = None)
/** UserComponent provides database definitions for User objects */
trait UserComponent { this: DriverComponent with PictureComponent =>
import driver.simple._
class Users(tag: Tag) extends Table[(String, Int, Option[Int])](tag, "USERS") {
def id = column[Option[Int]]("USER_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("USER_NAME", O.NotNull)
def pictureId = column[Int]("PIC_ID", O.NotNull)
def * = (name, pictureId, id)
}
val users = TableQuery[Users]
private val usersAutoInc =
users.map(u => (u.name, u.pictureId)) returning users.map(_.id)
def insert(user: User)(implicit session: Session): User = {
val pic =
if(user.picture.id.isEmpty) insert(user.picture)
else user.picture
val id = usersAutoInc.insert(user.name, pic.id.get)
user.copy(picture = pic, id = id)
}
}
Firstly, I'm assuming you've got a typo in TypeMappersTrait and Driver should in fact be DriverComponent (so this self-type matches annotation up with UserComponent).
Then you can just un-nest your implicit type mappers from object TypeMappers so that they're sitting directly inside TypeMappersTrait, and pull them in via:
trait UserComponent extends TypeMappersTrait

Is there a way to create a Clustered Index in Slick?

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