How can I use case classes with slicks TSQL interpolator? - scala

The sql interporlator allows case classes as a result given a GetResult instance
val action:DBIO[Seq[VisibilityRow]] = sql"SELECT * FROM visibility".as[VisibilityRow] //compiles fine
Checking the docs, I don't see an example of utilizing tsql along with a case class.
I decided to just try it out
val action2:DBIO[Seq[VisibilityRow]] = tsql"SELECT * FROM visibility"
However I am getting errors
[error] ... : type mismatch;
[error] found : slick.profile.SqlStreamingAction[Vector[(Long, String, java.sql.Timestamp, java.sql.Timestamp, java.sql.Timestamp)],(Long, String, java.sql.Timestamp, java.sql.Timestamp, java.sql.Timestamp),slick.dbio.Effect]
[error] required: slick.driver.PostgresDriver.api.DBIO[Seq[com.fevo.slick.Schema.VisibilityRow]]
[error] (which expands to) slick.dbio.DBIOAction[Seq[com.fevo.slick.Schema.VisibilityRow],slick.dbio.NoStream,slick.dbio.Effect.All]
[error] val action2:DBIO[Seq[VisibilityRow]] = tsql"SELECT * FROM visibility"
Is there an elegant way for me to extract these types using tsql, I could map over the apply but then I still need to specify (and update) the tuple type for DBIO's parameter accordingly.

If the case class constructor matches your fields, you can simply map over the result to get the type you need:
val action2:DBIO[Seq[VisibilityRow]] =
tsql"SELECT * FROM visibility".map(_.map(VisibilityRow.apply.tupled))

Related

How to write a symmetric Play Json formatter for a case class with one field in scala? [duplicate]

This question already has answers here:
How to turn json to case class when case class has only one field
(4 answers)
Closed 5 years ago.
Suppose I have some case class with one field
case class Id(value: String)
Trivially, I can define a formatter by defining the Reads and Writes separately:
private implicit val idReads: Reads[Id] =
JsPath.read[String].map(Id)
private implicit val idWrites: Writes[Id] =
{
id: Id => JsString(id.value)
}
private idFormats: Format[Id] = Format(idReads, idWrites)
The documentation suggests there is a way to define a symmetric formatter for this scenario, but I haven't found the specific incantation which makes it work for this case. I've tried the below, but I get a compile error:
private implicit val idFormats: Format[Id] =
JsPath.format[String](Id, unlift(Id.unapply))
Specifically, I get this compile error:
[error] overloaded method value format with alternatives:
[error] (w: play.api.libs.json.Writes[String])(implicit r: play.api.libs.json.Reads[String])play.api.libs.json.OFormat[String] <and>
[error] (r: play.api.libs.json.Reads[String])(implicit w: play.api.libs.json.Writes[String])play.api.libs.json.OFormat[String] <and>
[error] (implicit f: play.api.libs.json.Format[String])play.api.libs.json.OFormat[String]
[error] cannot be applied to (Id.type, Id => String)
[error] JsPath.format[String](Id, unlift(Id.unapply))
[error] ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 1 s, completed Nov 13, 2017 5:07:58 PM
I've read the documentation, but it hasn't helped me. I'm certain there is some one-liner that can be applied for this case, as it is trivial for case classes with two fields:
case class MyRow(id: Id, myNum: MyNum)
private implicit val myRowFormats: Format[MyRow] =
((JsPath \ "id").format[Id] and
(JsPath \ "num").format[MyNum]) (MyRow, unlift(MyRow.unapply))
If you really want Id to be serialized as a JSON string, here you go:
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Id(value: String)
implicit val idFormats: Format[Id] =
implicitly[Format[String]].inmap(Id, unlift(Id.unapply))
Json.toJson(Id("asd")) == JsString("asd")
Json.toJson(Id("asd")).toString == "\"asd\""
Json.parse(Json.toJson(Id("asd")).toString).as[Id] == Id("asd")
I wrote it this way to clearly illustrate that you use nothing else that a basic String formatter, which is defined in play-json.

How to make method generic without getting "No matching Shape found"

I am not sure how to get past this "No matching Shape found" error, apart from writing lots of boilerplate.
The basic idea illustrated in the Gist is that I have a very basic version of a method (works, but is very specific), then a version that takes the mapper parameter and is more generic (works too, but is specific to one particular type), and then a third version which takes a type parameter and would be very useful, but doesn't compile because of this error.
Basic method:
def updatePD_FirstNames(id: ids.PersonalDetailsId, firstNames: StringLtd30): Future[Int] = {
Better method:
def updatePD_SL(id: ids.PersonalDetailsId, mapper: tables.PersonalDetails => tables.profile.api.Rep[StringLtd30], sl: StringLtd30): Future[Int] = {
Ideal method (but doesn't compile):
def updatePD_X[X](id: ids.PersonalDetailsId, mapper: tables.PersonalDetails => tables.profile.api.Rep[X], sl: X): Future[Int] = {
```
[server] $ compile
[info] Compiling 1 Scala source to ... target\scala-2.12\classes...
[error] ...schema\DbProxy.scala:688: No matching Shape found.
[error] Slick does not know how to map the given types.
[error] Possible causes: T in Table[T] does not match your * projection,
[error] you use an unsupported type in a Query (e.g. scala List),
[error] or you forgot to import a driver api into scope.
[error] Required level: slick.lifted.FlatShapeLevel
[error] Source type: slick.lifted.Rep[X]
[error] Unpacked type: T
[error] Packed type: G
[error] val q2: Query[tables.profile.api.Rep[X], X, Seq] = q1.map(mapper)
[error] ^
[error] one error found
[error] (server/compile:compileIncremental) Compilation failed
[error] Total time: 4 s, completed 23-Mar-2017 11:15:47
```
Full code at https://gist.github.com/aholland/0845bf29d836d672d006ab58f5f1c73c
The only obvious problem I can see in the code you've posted is that X is unconstrained. It could be any type, includes ones that Slick doesn't know how to process.
What you can do is add a context bound on X. The bound you probably want is BaseTypedType, which is a "typed type" Slick uses to identify types it can work with. It's described from 11:30 in https://www.youtube.com/watch?v=tS6N5AaZTLA
You'd use it like this:
import slick.ast.BaseTypedType
def updatePD[X : BaseTypedType](
id: Long,
selector: PersonTable => Rep[X],
newValue: X
): DBIO[Int] =
people.filter(_.id === id).map(selector).update(newValue)
What that means is that when you use the method...
updatePD(anId, _.name, "Alice")
...the compiler has to prove to itself that whatever X you use, there is an approproate type representation in Slick.
This is also from Richard, but the exchange took place on gitter.
The only trouble with the first answer is that by demanding an implicit of type BaseTypedType[X] the context bound forces client code for optional columns to provide an implicit of type BaseTypedType[Option[X]] even when BaseTypedType[X] is already available.
This is unnecessary. Slick handles optional columns for you and if you provide an implicit for BaseTypedType[X] you are providing enough for it to handle columns of type Option[X].
So the context bound, while it works, is more demanding than necessary and results in having to write implicits in the client-code that involve directly referencing null and replicating logic already built into Slick. Not good.
The answer is to declare the implicit parameter as a named implicit parameter (called shape below) in its own parameter list, i.e. in long-form, not using the context bound short-hand :BaseTypedType. Then you can specify the more complicated but less demanding constraint used below.
So the solution is:
def updatePD[X] (id: Long, selector: PersonTable => Rep[X], newValue: X)
(implicit shape: Shape[_ <: FlatShapeLevel, Rep[X], X, _]): DBIO[Int] = {
people.filter(_.id === id).map(selector).update(newValue)
}
Understanding why shape has the exact type Shape[_ <: FlatShapeLevel, Rep[X], X, _] depends on an intimate understanding of Slick's types and implicit mechanisms. Richard may yet write a blog post on that!

slick filter or where no longer support logical operations?

I use slick 2.0.2 and I just want to do a simple filter or use a where sub-statement, I just want to do the logical operations like "and", "or" and "not" inside the filter :
val subjectdata = TableQuery[SubjectTable]
...
subjectdata.where(i=>(i.id===id && i.userId===rs.user.get.identityId.userId)).list()
and get error:
[error] G:\testprojects\slickplay\app\controllers\ShopController.scala:89: Cannot perform option-mapped operation
[error] with type: (Long, String) => R
[error] for base type: (Long, Long) => Boolean
[error] subjectdata.where(i=>(i.id===id && i.userId===rs.user.get.identityId
.userId)).list()
[error]
^
In slick 1.0.1 I can do:
val results = Query(TableClass)
.filter(r => r.isNull || r.expires > new Timestamp(DateTime.now().getMillis()))
.list
I want to do something similar on TableQuery in Slick2. How to do it?
One thing to know is that Slick's operations are more strict about types than Scala's. Both operands have to have the same base type, optionally wrapped in an Options. So comparing a Double to Double or an Option[Double] is ok, but comparing it to an Int will give you such a compile time warning. The error message hints a bit you towards the problem
[error] G:\testprojects\slickplay\app\controllers\ShopController.scala:89: Cannot perform option-mapped operation
[error] with type: (Long, String) => R
[error] for base type: (Long, Long) => Boolean
[error] subjectdata.where(i=>(i.id===id && i.userId===rs.user.get.identityId
.userId)).list()
In (Long, String) => R you see that the arguments do not have matching types and that the return type cannot be determined. So I assume either id or rs.user.get.identityId is a String. Turn is into an Int using .toInt. Alternatively you can convert the db-side value using .asColumnOf[String].

slick 2.0 define generic `find by field` method

import scala.slick.driver.MySQLDriver.simple._
class RichTable[T](tag: Tag, name: String) extends Table[T](tag, name) {
case class QueryExt[B](q: Query[RichTable.this.type, B]) {
def whereEq[C](col: RichTable.this.type => Column[C], c: C) = {
q.filter { fields =>
col(fields) === c
}
}
}
}
Then it complains
[error] /home/jilen/workspace/play-slick/src/main/scala/play/slick/SlickQueryExtension.scala:10: value === is not a member of slick.driver.MySQLDriver.simple.Column[C]
[error] col(fields) === c
[error] ^
[error] /home/jilen/workspace/play-slick/src/main/scala/play/slick/SlickQueryExtension.scala:9: ambiguous implicit values:
[error] both value BooleanColumnCanBeQueryCondition in object CanBeQueryCondition of type => scala.slick.lifted.CanBeQueryCondition[scala.slick.lifted.Column[Boolean]]
[error] and value BooleanOptionColumnCanBeQueryCondition in object CanBeQueryCondition of type => scala.slick.lifted.CanBeQueryCondition[scala.slick.lifted.Column[Option[Boolean]]]
[error] match expected type scala.slick.lifted.CanBeQueryCondition[Nothing]
[error] q.filter { fields =>
[error] ^
[error] two errors found
[error] (compile:compile) Compilation failed
[error] Total time: 0 s, completed Mar 6, 2014 1:21:48 AM
There have been questions about this, but the answers did not work for 2.0
How to parametrize Scala Slick queries by WHERE clause conditions?
Slick doesn't have any information about C, so it doesn't know if it can and how it should map it to a database value and if it can use === on it. So you get a type error. You will have to use Scala's type system to restrict the type to one for which Slick knows how to map it. You can do this by providing a so-called Context Bound, in this case :BaseColumnType.
def whereEq[C:BaseColumnType](col: RichTable.this.type => Column[C], c: C) = {
q.filter { fields =>
col(fields) === c
}
}
BaseColumnType is provided by Slick and using it in this way basically tells the Scala compiler to look for an implicit value of type BaseColumnType[C] in scope, where you call whereEq. Because then it is usually known what C will actually be. Slick comes with BaseColumnType[Int], BaseColumnType[String], etc. so at the call site, the Scala compiler can find one when your C is really an Int or String in that particular call and this way pass the info further to Slick.
Same for LiuTiger's question. abstract class Crud[..., PK:BaseColumnType] should do the trick, a trait doesn't work with context bounds. When implementing an abstract DAO be prepared to face a lot of challenges and get to the edges of your Scala type system skills and learn quite a bit about type inference order, implicit parameters, etc.

Type issue with Scala when dealing with timestamps

Straight forward problem that I don't see what I'm doing wrong in - some type mismatch somewhere. Basically trying to set a default datatype of Long on parameters that are coming in from a web request. Here's the code:
val startTs:Long = params.getOrElse("start_ts", DateTime.yesterdayAsEpoch).toLong
val endTs:Long = params.getOrElse("end_ts", DateTime.todayAsEpoch).toLong
My DateTime helper code:
def todayAsEpoch: Long = {
val c = Calendar.getInstance(TimeZone.getTimeZone("EST"))
c.setTime(new java.util.Date())
c.set(c.get(Calendar.YEAR),c.get(Calendar.MONTH),c.get(Calendar.DAY_OF_MONTH),0,0,0)
c.getTimeInMillis / 1000L
}
def yesterdayAsEpoch: Long = {
val c = Calendar.getInstance(TimeZone.getTimeZone("EST"))
c.setTime(new java.util.Date())
c.set(c.get(Calendar.YEAR),c.get(Calendar.MONTH),c.get(Calendar.DAY_OF_MONTH),0,0,0)
((c.getTimeInMillis / 1000L) - 86400)
}
And finally, the error:
value toLong is not a member of Any
[error] val startTs:Long = params.getOrElse("start_ts", DateTime.yesterdayAsEpoch).toLong
[error] ^
[error] /vagrant/src/main/scala/com/myapp/api/controllers/FooController.scala:437: value toLong is not a member of Any
[error] val endTs:Long = params.getOrElse("end_ts", DateTime.todayAsEpoch).toLong
[error] ^
[error] two errors found
[error] (compile:compile) Compilation failed
You did not say what params is. It looks like it might be a Map[String, X] with some type X. params.getOrElse(key, someLong) will considered to have the best common supertype of X and Long which happens to be Any, according to the error message, and which has no toLong method. As your default value happens to be Long already, and so don't need to be converted, I guess there is a toLong method on X.
If it is so, then you should convert the value retrieved from params to Long (when there is such a value), before providing the default value. That would be :
params.get("key").map(_.toLong).getOrElse(defaultValue)
I'm guessing params is a Map[String, Something], and that Something isn't always a numeric type. (String?) In any case, when you call params.getOrElse, it's inferring a common type between Something and Long, and finding Any, which is why you can't call toLong on it.