Sangria: How to handle custom types - scala

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]

Related

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?

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?

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

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

Scala type resolution of inner class

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