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.
Related
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 <>
This is given:
case class Money(value: BigDecimal, currency: Currency)
trait Currency
case class EUR ( ... ) extends Currency
... and so on
I want to model a optional Money type which contains a value part (BigDecimal) and a Currency part (a Enum through Enumeratum).
To map this in the DB, I have two optional columns:
private def moneyOptionalValue: Rep[Option[BigDecimal]] = column[Option[BigDecimal]]("money_value")
private def moneyOptionalCurrency: Rep[Option[Currency]] = column[Option[Currency]]("money_currency")
The desired state would be to have this:
def money: Rep[Option[Money]] = ...
I tried with this approach, as documented in
http://slick.lightbend.com/doc/3.1.0/userdefined.html#using-custom-record-types-in-queries
private implicit def myCurrencyType: BaseColumnType[Currency] = MappedColumnType.base[Currency, String](
c => c.shortName,
s => Currency.withNameUppercaseOnly(s.toUpperCase)
)
case class LiftedMoney(value: Rep[BigDecimal], currency: Rep[Currency])
case class LiftedMoneyOptional(a: Rep[Option[BigDecimal]], b: Rep[Option[Currency]])
case class MoneyOptional(a: Option[BigDecimal], b: Option[Currency])
// custom case class mapping
implicit object MoneyOptionalShape extends CaseClassShape(LiftedMoneyOptional.tupled, MoneyOptional.tupled)
implicit object MoneyShape extends CaseClassShape(LiftedMoney.tupled, Money.tupled)
implicit object MoneyToOptional
extends Isomorphism[Option[Money], MoneyOptional](
{
case Some(m) => MoneyOptional(Some(m.value), Some(m.currency))
case None => MoneyOptional(None, None)
}, {
case MoneyOptional(Some(v), Some(c)) => Some(Money(v, c))
case MoneyOptional(_, _) => None
}
)
private def moneyOptionalValue: Rep[Option[BigDecimal]] = column[Option[BigDecimal]]("money_value")
private def moneyOptionalCurrency: Rep[Option[Currency]] = column[Option[Currency]]("money_currency")
def moneyOptional: Rep[MoneyOptional] =
moneyOptionalValue.zip(moneyOptionalCurrency).mapTo[MoneyOptional]
def moneyOptional1: MappedProjection[MoneyOptional, (Option[BigDecimal], Option[Currency])] =
moneyOptionalValue.zip(moneyOptionalCurrency).mapTo[MoneyOptional]
// this compiles, if its not optional, but would break at runtime, when null values are in the columns:
private def moneyValue: Rep[BigDecimal] = column[BigDecimal]("money_value") // this column can be nullable!
private def moneyCurrency: Rep[Currency] = column[Currency]("money_currency") // // this column can be nullable!
def money: Rep[Money] = moneyValue.zip(moneyCurrency).value.mapTo[Money]
// I also tried this, but cannot compile my code with that for some reason
def optionMoney: Rep[Option[Money]] = RepOption[Money](money, money.toNode)
Any help would be greatly appreciated!
What about this one ?
def money = (moneyOptionalValue, moneyOptionalCurrency) <> [Option[Money]](
{
case (value,currency) if value.isDefined && currency.isDefined => Some(Money(value.get, currency.get))
case _ => None
},
(mapped:Option[Money]) => mapped.map( m => Some(Some(m.value), Some(m.currency))).getOrElse(Some(None,None))
)
I want to auto-generate REST API models in Scala using scalameta annotation macros. Specifically, given:
#Resource case class User(
#get id : Int,
#get #post #patch name : String,
#get #post email : String,
registeredOn : Long
)
I want to generate:
object User {
case class Get(id: Int, name: String, email: String)
case class Post(name: String, email: String)
case class Patch(name: Option[String])
}
trait UserRepo {
def getAll: Seq[User.Get]
def get(id: Int): User.Get
def create(request: User.Post): User.Get
def replace(id: Int, request: User.Put): User.Get
def update(id: Int, request: User.Patch): User.Get
def delete(id: Int): User.Get
}
I have something working here: https://github.com/pathikrit/metarest
Specifically I am doing this:
import scala.collection.immutable.Seq
import scala.collection.mutable
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.meta._
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
#compileTimeOnly("#metarest.Resource not expanded")
class Resource extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
val (cls: Defn.Class, companion: Defn.Object) = defn match {
case Term.Block(Seq(cls: Defn.Class, companion: Defn.Object)) => (cls, companion)
case cls: Defn.Class => (cls, q"object ${Term.Name(cls.name.value)} {}")
case _ => abort("#metarest.Resource must annotate a class")
}
val paramsWithAnnotation = for {
Term.Param(mods, name, decltype, default) <- cls.ctor.paramss.flatten
seenMods = mutable.Set.empty[String]
modifier <- mods if seenMods.add(modifier.toString)
(tpe, defArg) <- modifier match {
case mod"#get" | mod"#put" | mod"#post" => Some(decltype -> default)
case mod"#patch" =>
val optDeclType = decltype.collect({case tpe: Type => targ"Option[$tpe]"})
val defaultArg = default match {
case Some(term) => q"Some($term)"
case None => q"None"
}
Some(optDeclType -> Some(defaultArg))
case _ => None
}
} yield modifier -> Term.Param(Nil, name, tpe, defArg)
val models = paramsWithAnnotation
.groupBy(_._1.toString)
.map({case (verb, pairs) =>
val className = Type.Name(verb.stripPrefix("#").capitalize)
val classParams = pairs.map(_._2)
q"case class $className[..${cls.tparams}] (..$classParams)"
})
val newCompanion = companion.copy(
templ = companion.templ.copy(stats = Some(
companion.templ.stats.getOrElse(Nil) ++ models
))
)
Term.Block(Seq(cls, newCompanion))
}
}
I am unhappy with the following snip of code:
modifier match {
case mod"#get" | mod"#put" | mod"#post" => ...
case mod"#patch" => ...
case _ => None
}
The above code does "stringly" pattern matching on the annotations I have. Is there anyway to re-use the exact annotations I have to pattern match for these:
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
It's possible to replace the mod#get stringly typed annotation with a get() extractor using a bit of runtime reflection (at compile time).
In addition, let's say we also want to allow users to fully qualify the annotation with #metarest.get or #_root_.metarest.get
All the following code examples assume import scala.meta._. The tree structure of #get, #metarest.get and #_root_.metarest.get are
# mod"#get".structure
res4: String = """ Mod.Annot(Ctor.Ref.Name("get"))
"""
# mod"#metarest.get".structure
res5: String = """
Mod.Annot(Ctor.Ref.Select(Term.Name("metarest"), Ctor.Ref.Name("get")))
"""
# mod"#_root_.metarest.get".structure
res6: String = """
Mod.Annot(Ctor.Ref.Select(Term.Select(Term.Name("_root_"), Term.Name("metarest")), Ctor.Ref.Name("get")))
"""
The selectors are either Ctor.Ref.Select or Term.Select and the names are either Term.Name or Ctor.Ref.Name.
Let's first create a custom selector extractor
object Select {
def unapply(tree: Tree): Option[(Term, Name)] = tree match {
case Term.Select(a, b) => Some(a -> b)
case Ctor.Ref.Select(a, b) => Some(a -> b)
case _ => None
}
}
Then create a few helper utilities
object ParamAnnotation {
/* isSuffix(c, a.b.c) // true
* isSuffix(b.c, a.b.c) // true
* isSuffix(a.b.c, a.b.c) // true
* isSuffix(_root_.a.b.c, a.b.c) // true
* isSuffix(d.c, a.b.c) // false
*/
def isSuffix(maybeSuffix: Term, fullName: Term): Boolean =
(maybeSuffix, fullName) match {
case (a: Name, b: Name) => a.value == b.value
case (Select(q"_root_", a), b: Name) => a.value == b.value
case (a: Name, Select(_, b)) => a.value == b.value
case (Select(aRest, a), Select(bRest, b)) =>
a.value == b.value && isSuffix(aRest, bRest)
case _ => false
}
// Returns true if `mod` matches the tree structure of `#T`
def modMatchesType[T: ClassTag](mod: Mod): Boolean = mod match {
case Mod.Annot(term: Term.Ref) =>
isSuffix(term, termRefForType[T])
case _ => false
}
// Parses `T.getClass.getName` into a Term.Ref
// Uses runtime reflection, but this happens only at compile time.
def termRefForType[T](implicit ev: ClassTag[T]): Term.Ref =
ev.runtimeClass.getName.parse[Term].get.asInstanceOf[Term.Ref]
}
With this setup, we can add a companion object to the get definition with an
unapply boolean extractor
class get extends StaticAnnotation
object get {
def unapply(mod: Mod): Boolean = ParamAnnotation.modMatchesType[get](mod)
}
Doing the same for post and put, we can now write
// before
case mod"#get" | mod"#put" | mod"#post" => Some(decltype -> default)
// after
case get() | put() | post() => Some(decltype -> default)
Note that this approach will still not work if the user renames for example get on import
import metarest.{get => GET}
I would recommend aborting if an annotation does not match what you expected
// before
case _ => None
// after
case unexpected => abort("Unexpected modifier $unexpected. Expected one of: put, get post")
PS. The object get { def unapply(mod: Mod): Boolean = ... } part is boilerplate that could be generated by some #ParamAnnotation macro annotation, for example #ParamAnnotion class get extends StaticAnnotation
Having a table with the columns
class Data(tag: Tag) extends Table[DataRow](tag, "data") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def state = column[State]("state")
def price = column[Int]("price")
def * = (id.?, name, state, price) <> ((DataRow.apply _).tupled, DataRow.unapply)
}
I'd like to write a function that would select a single row, and update the columns where the supplied values are not null.
def update(id: Int, name: Option[String], state: Option[State], price: Option[Int])
eg.
update(1, None, None, Some(5)) would update only the price of the data row 1, leaving the name and state intact
update(1, Some("foo"), None, Some(6)) would update the name and price, but leave its state intact.
I guess some smart mapping could be used, but I'm having a hard time expressing it, not sure how it could spit out different length tuples depending on the inputs (wether their value is defined), since they are more or less "unrelated" classes.
def update(id: Int, name: Option[String], state: Option[State], price: Option[Int]) = {
table.fiter(_.id == id). ???? .update(name, state, price)
}
I solved it in the following way.
The implementation below works only if it is a Product object.
Execute the update statement except for None for the Option type and null for the object type.
package slick.extensions
import slick.ast._
import slick.dbio.{ Effect, NoStream }
import slick.driver.JdbcDriver
import slick.jdbc._
import slick.lifted._
import slick.relational.{ CompiledMapping, ProductResultConverter, ResultConverter, TypeMappingResultConverter }
import slick.util.{ ProductWrapper, SQLBuilder }
import scala.language.{ existentials, higherKinds, implicitConversions }
trait PatchActionExtensionMethodsSupport { driver: JdbcDriver =>
trait PatchActionImplicits {
implicit def queryPatchActionExtensionMethods[U <: Product, C[_]](
q: Query[_, U, C]
): PatchActionExtensionMethodsImpl[U] =
createPatchActionExtensionMethods(updateCompiler.run(q.toNode).tree, ())
}
///////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////// Patch Actions
///////////////////////////////////////////////////////////////////////////////////////////////
type PatchActionExtensionMethods[T <: Product] = PatchActionExtensionMethodsImpl[T]
def createPatchActionExtensionMethods[T <: Product](tree: Node, param: Any): PatchActionExtensionMethods[T] =
new PatchActionExtensionMethodsImpl[T](tree, param)
class PatchActionExtensionMethodsImpl[T <: Product](tree: Node, param: Any) {
protected[this] val ResultSetMapping(_, CompiledStatement(_, sres: SQLBuilder.Result, _),
CompiledMapping(_converter, _)) = tree
protected[this] val converter = _converter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, Product]]
protected[this] val TypeMappingResultConverter(childConverter, toBase, toMapped) = converter
protected[this] val ProductResultConverter(elementConverters # _ *) =
childConverter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, Product]]
private[this] val updateQuerySplitRegExp = """(.*)(?<=set )((?:(?= where)|.)+)(.*)?""".r
private[this] val updateQuerySetterRegExp = """[^\s]+\s*=\s*\?""".r
/** An Action that updates the data selected by this query. */
def patch(value: T): DriverAction[Int, NoStream, Effect.Write] = {
val (seq, converters) = value.productIterator.zipWithIndex.toIndexedSeq
.zip(elementConverters)
.filter {
case ((Some(_), _), _) => true
case ((None, _), _) => false
case ((null, _), _) => false
case ((_, _), _) => true
}
.unzip
val (products, indexes) = seq.unzip
val newConverters = converters.zipWithIndex
.map(c => (c._1, c._2 + 1))
.map {
case (c: BaseResultConverter[_], idx) => new BaseResultConverter(c.ti, c.name, idx)
case (c: OptionResultConverter[_], idx) => new OptionResultConverter(c.ti, idx)
case (c: DefaultingResultConverter[_], idx) => new DefaultingResultConverter(c.ti, c.default, idx)
case (c: IsDefinedResultConverter[_], idx) => new IsDefinedResultConverter(c.ti, idx)
}
val productResultConverter =
ProductResultConverter(newConverters: _*).asInstanceOf[ResultConverter[JdbcResultConverterDomain, Any]]
val newConverter = TypeMappingResultConverter(productResultConverter, (p: Product) => p, (a: Any) => toMapped(a))
val newValue: Product = new ProductWrapper(products)
val newSql = sres.sql match {
case updateQuerySplitRegExp(prefix, setter, suffix) =>
val buffer = StringBuilder.newBuilder
buffer.append(prefix)
buffer.append(
updateQuerySetterRegExp
.findAllIn(setter)
.zipWithIndex
.filter(s => indexes.contains(s._2))
.map(_._1)
.mkString(", ")
)
buffer.append(suffix)
buffer.toString()
}
new SimpleJdbcDriverAction[Int]("patch", Vector(newSql)) {
def run(ctx: Backend#Context, sql: Vector[String]): Int =
ctx.session.withPreparedStatement(sql.head) { st =>
st.clearParameters
newConverter.set(newValue, st)
sres.setter(st, newConverter.width + 1, param)
st.executeUpdate
}
}
}
}
}
Example
// Model
case class User(
id: Option[Int] = None,
name: Option[String] = None,
username: Option[String] = None,
password: Option[String] = None
)
// Table
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def username = column[String]("username")
def password = column[String]("password")
override def * = (id.?, name.?, username.?, password.?) <>(User.tupled, User.unapply)
}
// TableQuery
object Users extends TableQuery(new Users(_))
// CustomDriver
trait CustomDriver extends PostgresDriver with PatchActionExtensionMethodsSupport {
override val api: API = new API {}
trait API extends super.API with PatchActionImplicits
}
// Insert
Users += User(Some(1), Some("Test"), Some("test"), Some("1234"))
// User patch
Users.filter(_.id === 1).patch(User(name = Some("Change Name"), username = Some("")))
https://gist.github.com/bad79s/1edf9ea83ba08c46add03815059acfca
Building on JonasAnso's answer, converting that to slick v3.0+, and putting it into a transaction:
def partialUpdate(id: Int, name: Option[String], login: Option[String]): Future[Int] = {
val selectQ = users.filter(_.id === id)
val query = selectQ.result.head.flatMap { data =>
selectQ.update(data.patch(name, login))
}
db.run(query)
}
As I commented the question is similar to an existing one, but you don't seem to have any extra requirements.
The simplest approach is just SELECT + UPDATE. For example you add a patch function in your DataRow class defining how you want to update your model
def patch(name: Option[String], state: Option[State], price: Option[Int]): Data {
this.copy(name = name.getOrElse(this.name), ...)
}
And you add a partialUpdate method in your repo class
class DataRepo {
private val Datas = TableQuery[Data]
val db = ???
def partialUpdate(id: Int, name: Option[String], state: Option[State], price: Option[Int]): Future[Int] = {
val query = Datas.filter(_.id === id)
for {
data <- db.run(query.result.head)
result <- db.run(query.update(data.patch(name, state, price)))
} yield result
}
}
As you see the main problem of this solution is that there are 2 SQL statements, SELECT and UPDATE.
Other solution is to use plain SQL (http://slick.typesafe.com/doc/3.0.0/sql.html) but of course this gives other problems.
I am using SLICK 1.0.0-RC2. I have defined the following two tables Directorate and ServiceArea where Directorate has a one to many relationship with ServiceArea
case class Directorate(dirCode: String, name: String)
object Directorates extends Table[Directorate]("DIRECTORATES") {
def dirCode = column[String]("DIRECTORATE_CODE", O.PrimaryKey)
def name = column[String]("NAME")
def * = dirCode ~ name <> (Directorate, Directorate.unapply _)
}
case class ServiceArea(areaCode: String, dirCode: String, name: String)
object ServiceAreas extends Table[ServiceArea]("SERVICE_AREAS") {
def areaCode = column[String]("AREAE_CODE", O.PrimaryKey)
def dirCode = column[String]("DIRECTORATE_CODE")
def name = column[String]("NAME")
def directorate = foreignKey("DIR_FK", dirCode, Directorates)(_.dirCode)
def * = areaCode ~ dirCode ~ name <> (ServiceArea, ServiceArea.unapply _)
}
To make the Directorate case class useful in my Play application form I am trying to redefine the Directorate case class to have a Seq of ServiceAreas that are related to that Directorate.
case class Directorate(dirCode: String, name: String, serviceAreas: Seq[ServiceArea])
My problem is now with the Directorate table projection. I have attempted to create a method in Directorates:
def serviceAreas = (for { a <- ServiceAreas
if (a.dirCode === dirCode)
} yield (a)).list map {
case t: ServiceArea => t
}
so that I can try something like
def * = dirCode ~ name ~ serviceAreas <> (Directorate, Directorate.unapply _)
but this cannot not work as serviceAreas only goes one way.
It seems reasonable to me that for the Directorate case class to be a useful domain object that it should be able contain the related ServiceAreas.
I'm wondering how I should traverse the inverse relationship so that Directorate table projection will work.
I'm sure there is a more elegant solution, but this should do the trick:
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
object SlickExperiments2 {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
(Directorates.ddl ++ ServiceAreas.ddl).create
case class Directorate(dirCode: String, name: String) {
def serviceAreas: Seq[ServiceArea] = (for {
a <- ServiceAreas
if (a.dirCode === dirCode)
} yield (a)).list
}
object Directorates extends Table[Directorate]("DIRECTORATES") {
def dirCode = column[String]("DIRECTORATE_CODE", O.PrimaryKey)
def name = column[String]("NAME")
def * = dirCode ~ name <> (Directorate, Directorate.unapply _)
}
case class ServiceArea(areaCode: String, dirCode: String, name: String)
object ServiceAreas extends Table[ServiceArea]("SERVICE_AREAS") {
def areaCode = column[String]("AREAE_CODE", O.PrimaryKey)
def dirCode = column[String]("DIRECTORATE_CODE")
def name = column[String]("NAME")
def directorate = foreignKey("DIR_FK", dirCode, Directorates)(_.dirCode)
def * = areaCode ~ dirCode ~ name <> (ServiceArea, ServiceArea.unapply _)
}
Directorates.insert(Directorate("Dircode", "Dirname"))
ServiceAreas.insertAll(ServiceArea("a", "Dircode", "A"), ServiceArea("b", "Dircode", "B"))
val sa = (for{
d <- Directorates
} yield d).list map { case t: Directorate => t.serviceAreas}
println(sa)
}
//> List(List(ServiceArea(a,Dircode,A), ServiceArea(b,Dircode,B)))
}