I have a case class, mappings and the object defined like this
import slick.driver.PostgresDriver.api._
import scala.concurrent.Await
import scala.concurrent.duration.Duration
case class Coffee(name : String, supID: Int, price: Double, sales: Int, total: Int) {
def save(): Coffee = { Coffees.save(this) }
def delete() { Coffees.delete(this) }
}
class Coffees(tag: Tag) extends Table[Coffee](tag, "coffee") {
def name = column[String]("cof_name", O.PrimaryKey)
def supID = column[Int]("sup_id")
def price = column[Double]("price")
def sales = column[Int]("sales", O.Default(0))
def total = column[Int]("total", O.Default(0))
def * = (name, supID, price, sales, total) <> (Coffee.tupled, Coffee.unapply)
}
object Coffees extends TableQuery(new Coffees(_)) {
lazy val db = DatabaseAccess.databases("db.test")
def save(coffee: Coffee): Coffee = {
val saveQuery = (this returning this).insertOrUpdate(coffee)
Await.result(db.run(saveQuery), Duration.Inf) match {
case Some(x) => x
case None => coffee
}
}
def delete(coffee: Coffee) = {
Await.result(db.run(this.filter(_.name === coffee.name).delete), Duration.Inf)
}
}
Now I want to write queries from other classes like this :
import com.typesafe.config.ConfigFactory
import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers}
class CoffeeSpec extends FeatureSpec with GivenWhenThen with Matchers {
feature("Accessing coffees in the database") {
scenario("Adding a new coffee") {
val config = ConfigFactory.load()
DatabaseAccess.loadConfiguration(config)
lazy val db = DatabaseAccess.databases("db.test")
val coffee = Coffee("nescafe", 1, 20, 20, 40).save()
val temp = Coffees.filter(_.name === "nescafe")
temp.length should be (1)
coffee.delete()
}
}
}
This line
val temp = Coffees.filter(_.name === "nescafe")
Throws an error like this:
What is the best way to write the filter queries on the Coffees object?
Using slick 2.0 I have queries like:
Query(Coffees).filter(_.name is "coffee").firstOption.getOrElse(None)
I want to be able to do the similar queries using the new setup for all the data mappings.
Why am I getting these errors and how should I be able to make similar queries in slick 3?
Your queries are fine, but your CoffeeSpec file is missing an import:
import slick.driver.PostgresDriver.api._
Compilation should pass then.
Related
I am using play 2.6.6 , scala 2.12.3 and slick 3.0.0.
I had following case class structure initially where there was a nested case class:
case class Device(id: Int, deviceUser: Option[DeviceUser] =None)
case class DeviceUser(name: Option[String] = None)
So, I had created following projection for Device class:
class DevicesTable(tag: Tag) extends Table[Device](tag, "DEVICES") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[Option[String]]("NAME")
def deviceUser = name.<>[Option[DeviceUser]](
{
(param: Option[String]) => {
param match {
case Some(name) => Some(DeviceUser(Some(name)))
case None => None
}
}
},
{
(t: Option[DeviceUser]) =>
{
t match {
case Some(user) => Some(user.name)
case None => None
}
}
}
)
def * = (id, deviceUser).<>(Device.tupled, Device.unapply)
}
The above setup was working fine. I could easily store and retrieve data using the above projection. But now, my requirement has changed and I need to store list of nested case class. So, the class structure is now as follow :
case class Device(id: Int, deviceUser: Option[List[DeviceUser]] =None)
case class DeviceUser(name: Option[String] = None)
Is there some way where I could define projection for the field deviceUser: Option[List[DeviceUser]] ?
Update : I am looking for more of a non-relational approach here.
Since, no body has suggested a solution so far, I am sharing the approach that I am using right now. It works but of course is not the best solution. Specially, I want to avoid using Await here and would like to develop a generic implicit parser.
ALso, I had to create a separate DeviceUsersTable.
case class DeviceUser(id: Int,name: Option[String] = None)
class DeviceUserRepo #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) {
val dbConfig = dbConfigProvider.get[JdbcProfile]
val db = dbConfig.db
import dbConfig.profile.api._
val DeviceUsers = TableQuery[DeviceUserTable]
private def _findById(id: Int): DBIO[Option[DeviceUser]] =
DeviceUsers.filter(_.id === id).result.headOption
def findById(id: Int): Future[Option[DeviceUser]] =
db.run(_findById(id))
def all: Future[List[DeviceUser]] =
db.run(DeviceUsers.to[List].result)
def create(deviceUser: DeviceUser): Future[Int] = {
db.run(DeviceUsers returning DeviceUsers.map(_.id) += deviceUser)
}
class DeviceUserTable(tag: Tag) extends Table[DeviceUser](tag, "DEVICE_USERS") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[Option[String]]("NAME")
def * = (id, name).<>(DeviceUser.tupled, DeviceUser.unapply)
}
}
And the original DevicesTable now looks like this :
class DevicesTable(tag: Tag) extends Table[Device](tag, "DEVICES") {
implicit val deviceUserConverter = MappedColumnType.base[Option[List[DeviceUser]], String](
deviceUsersOpt => {
deviceUsersOpt match {
case Some(users:List[DeviceUser]) =>val listOfId = users.map{
k => val res = deviceUserRepo.create(k)
Await.result(res, 10 seconds)
}
listOfId.mkString(",")
case None => ""
}
},
str =>{
val listOfIds = (str split "," map Integer.parseInt).toList.filterNot(k => k.equals(""))
if(listOfIds.nonEmpty){
val users = listOfIds.map{ k =>
val res = deviceUserRepo.findById(k)
Await.result(res, 10 seconds)
}
Some(users.flatten)
} else {
None
}
}
)
def id = column[Int]("ID", O.PrimaryKey)
def deviceUser = column[Option[List[DeviceUser]]]("DEVICE_USERS")
def * = (id, deviceUser).<>(Device.tupled, Device.unapply)
}
We have a Scala application using Scalatra (http://scalatra.org/) as our web framework. I'm wondering if there are any good (or just any) resources out there on how to implement a GraphQL endpoint using Sangria (http://sangria-graphql.org/) and Scalatra?
I'm new to Scala and would appreciate any help to get started on this.
There aren't any that I know of but since Scalatra uses json4s you would use sangria's json4s marshaller .
Otherwise, if sangria could be clearer to you, here's a scala worksheet with a very simplistic example based off play + sangria - in this case you would just need to swap the json library.
The db is mocked (perhaps you use Slick?) and the http server as well but it's a simple case of swapping in the function definitions.
import sangria.ast.Document
import sangria.execution.{ErrorWithResolver, Executor, QueryAnalysisError}
import sangria.macros.derive.{ObjectTypeDescription, ObjectTypeName, deriveObjectType}
import sangria.parser.{QueryParser, SyntaxError}
import sangria.renderer.SchemaRenderer
import sangria.schema.{Argument, Field, IntType, ListType, ObjectType, OptionInputType, Schema, fields}
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success}
// replace with another json lib
// eg https://github.com/sangria-graphql/sangria-json4s-jackson
import play.api.libs.json._
import sangria.marshalling.playJson._
case class User(name: String, age: Int, phone: Option[String])
class FakeDb {
class UsersTable {
def getUsers(limit: Int): List[User] = {
// this would come from the db
List(
User("john smith", 23, None),
User("Anne Schwazenbach", 45, Some("2134556"))
)
}
}
val usersRepo = new UsersTable
}
object MySchema {
val limitArg: Argument[Int] = Argument("first", OptionInputType(IntType),
description = s"Returns the first n elements from the list.",
defaultValue = 10)
implicit val UsersType: ObjectType[FakeDb, User] = {
deriveObjectType[FakeDb, User](
ObjectTypeName("Users"),
ObjectTypeDescription("Users in the system")
)
}
private val Query: ObjectType[FakeDb, Unit] = ObjectType[FakeDb, Unit](
"Query", fields[FakeDb, Unit](
Field("users", ListType(UsersType),
arguments = limitArg :: Nil,
resolve = c => c.ctx.usersRepo.getUsers(c.arg(limitArg))
)
))
val theSchema: Schema[FakeDb, Unit] = Schema(Query)
}
object HttpServer {
def get(): String = {
// Http GET
SchemaRenderer.renderSchema(MySchema.theSchema)
}
def post(query: String): Future[JsValue] = {
// Http POST
val variables = None
val operation = None
QueryParser.parse(query) match {
case Success(q) => executeQuery(q, variables, operation)
case Failure(error: SyntaxError) => Future.successful(Json.obj("error" -> error.getMessage))
case Failure(error: Throwable) => Future.successful(Json.obj("error" -> error.getMessage))
}
}
private def executeQuery(queryAst: Document, vars: Option[JsValue], operation: Option[String]): Future[JsValue] = {
val schema: Schema[FakeDb, Unit] = MySchema.theSchema
Executor.execute[FakeDb, Unit, JsValue](schema, queryAst, new FakeDb,
operationName = operation,
variables=vars.getOrElse(Json.obj()))
.map((d: JsValue) => d)
.recover {
case error: QueryAnalysisError ⇒ Json.obj("error" -> error.getMessage)
case error: ErrorWithResolver ⇒ Json.obj("error" -> error.getMessage)
}
}
}
HttpServer.get()
val myquery = """
{
users {
name
}
}
"""
val res: JsValue = Await.result(HttpServer.post(myquery), 10.seconds)
I am using scala, play, sclick; and postgres. I have made a table named order & it contains a field named created in timestamp format with zone. Now I would like to search on order based on created with parameters year & month as follows:-
SELECT * FROM "order"
WHERE created::text LIKE '2016-07%';
The above query works fine in postgres.
The scala code I have written for Order is:-
case class Order(id: Option[Int],
customerId: Int,
amount: Double,
created: Option[Instant],
updated: Option[Instant]) extends GenericEntity {
def this(customerId: Int,
amount: Double,) = this(None, customerId, amount, None, None)
}
class OrderTable(tag: Tag) extends GenericTable[Order](tag, "order") {
override def id = column[Option[Int]]("id", O.PrimaryKey, O.AutoInc)
def customerId = column[Int]("customer_id")
def amount = column[Dount]("amount")
def customer = foreignKey("fk_order_customer", customerId, Customers.table)(_.id.getOrElse(1), onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
def * = (id, customerId, amount, created, updated) <> ((Order.apply _).tupled, Order.unapply)
}
object Orders extends GenericService[Order, OrderTable] {
override val table = TableQuery[OrderTable]
override def copyEntityFields(entity: Order, id: Option[Int], created: Option[Instant], updated: Option[Instant]): Order = {
entity.copy(id = id, created = created, updated = updated)
}
def getMonthlyOrder(year:Int, month: Int) = {
// execute LIKE query for the following query
// SELECT * FROM "order"
// WHERE created::text LIKE '2016-07%';
}
}
Where GenericEntity defines id, created, updated.
So what code should I write for getMonthlyOrder function with year and month?.
You need two things:
1) A column type to let Slick know how to persist Instant to database. In this case you want to map Instant to java.sql.Timestamp, which Slick can use natively.
implicit val instantColumnType: BaseColumnType[Instant] =
MappedColumnType.base[Instant, Timestamp](
instant => Timestamp.from(instant),
ts => ts.toInstant
)
2) Functions to extract year and month from your timestamp. Here I used another approach than your LIKE query. You could also define a mapping from Instant to String and use something like startsWith.
val yearFn = SimpleFunction.unary[Instant, Int]("year")
val monthFn = SimpleFunction.unary[Instant, Int]("month")
Then you would use them in getMonthlyOrder like this
def getMonthlyOrder(year: Int, month: Int): Future[Seq[Order]] = {
val query = table.filter(_.created.map((created) => yearFn(created) === year && monthFn(created) === month))
db.run(query.result)
}
The complete code valid for Slick 3.1:
import java.sql.Timestamp
import java.time.Instant
import javax.inject.{Inject, Singleton}
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.driver.JdbcProfile
import slick.lifted.TableQuery
import scala.concurrent.Future
case class Order(id: Int,
customerId: Int,
amount: Double,
created: Option[Instant],
updated: Option[Instant])
#Singleton
class Orders #Inject()(val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
val table = TableQuery[OrderTable]
import driver.api._
implicit val instantColumnType: BaseColumnType[Instant] =
MappedColumnType.base[Instant, Timestamp](
instant => Timestamp.from(instant),
ts => ts.toInstant
)
val yearFn = SimpleFunction.unary[Instant, Int]("year")
val monthFn = SimpleFunction.unary[Instant, Int]("month")
def getMonthlyOrder(year: Int, month: Int): Future[Seq[Order]] = {
val query = table.filter(_.created.map((created) => yearFn(created) === year && monthFn(created) === month))
db.run(query.result)
}
class OrderTable(tag: Tag) extends Table[Order](tag, "ORDERS") {
def * = (id, customerId, amount, created, updated) <> ((Order.apply _).tupled, Order.unapply)
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def customerId = column[Int]("CUSTOMER_ID")
def amount = column[Double]("AMOUNT")
def created = column[Option[Instant]]("CREATED")
def updated = column[Option[Instant]]("UPDATED")
}
}
I have AccountTable:
import models.{Account, Category}
import play.api.Play
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfig}
import slick.driver.JdbcProfile
import slick.driver.PostgresDriver.api._
import slick.lifted.Tag
import play.api.libs.json.{JsValue, Writes, Json}
object AccountTable extends AccountTable
trait AccountTable extends HasDatabaseConfig[JdbcProfile]{
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
class Accounts(tag: Tag) extends Table[Account](tag, "account") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def login = column[String]("login")
def password = column[String]("password")
def * = (id, login, password) <> ((Account.apply _).tupled, Account.unapply)
}
val accounts = TableQuery[Accounts]
implicit val accountFormat = Json.format[Account]
def auth(login: String, password: String): Boolean = {
db.run(findByLogin(login, password).result.headOption)
}
private def findByLogin(login: String, password: String) = accounts.filter(x => (x.password === password && x.login === login))
}
I try to develop auth method. I really don't understand, how to complete this method. I tried different ways, but always I get different errors.
Option 1:
def auth(login: String, password: String): Future[Boolean] = {
val action = findByLogin(login, password).result.headOption
.map(authOpt => authOpt.getOrElse(false))
db.run(action)
}
Option 2:
def auth(login: String, password: String): Boolean = {
val action = findByLogin(login, password).result.headOption
.map(authOpt => authOpt.getOrElse(false))
Await.result(db.run(action), 5 seconds)
}
The code above is untested, but you should get the idea behind queries in slick. I assumed that auth should return false if no result was found in the db.
One last word: I strongly recommend to use option 1 and work with Future, as Await.result blocks the thread from execution.
What is wrong with this ActiveSlick query?
import io.strongtyped.active.slick.ActiveSlick
import io.strongtyped.active.slick.models.Identifiable
import scala.slick.driver._
import scala.slick.driver.JdbcProfile._
trait MappingActiveSlickIdentifiable {
this: ActiveSlick =>
import jdbcDriver.simple._
case class Foo(name: String, id: Option[Int] = None) extends Identifiable[Foo] {
override type Id = Int
override def withId(id: Id): Foo = copy(id = Some(id))
}
class FooTable(tag: Tag) extends EntityTable[Foo](tag, "FOOS") {
def name = column[String]("NAME")
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def * = (name, id.?) <> (Foo.tupled, Foo.unapply)
}
val Foos = new EntityTableQuery[Foo, FooTable](tag => new FooTable(tag))
}
object MappingActiveSlickIdentifiable {
class Components(override val jdbcDriver: JdbcDriver)
extends ActiveSlick
with MappingActiveSlickIdentifiable {
import jdbcDriver.simple._
val db = Database.forURL("jdbc:h2:mem:active-slick", driver = "org.h2.Driver")
def createSchema(implicit s:Session): Unit = {
Foos.ddl.create
}
}
object Components {
val instance = new Components(H2Driver)
}
import Components.instance._
def main(args:Array[String]) : Unit = {
db.withTransaction { implicit s =>
createSchema
(1 to 3) foreach { i =>
val foo = Foo(s"foo $i")
val fooWithId : Foo = Foos.save(foo)
Foos.update(fooWithId.copy(name = "?"))
println(s"Foo: $foo, foo with id: $fooWithId")
assert(fooWithId.id.isDefined, "Foo's ID should be defined")
}
val q = for {
f <- Foos if f.name === "?"
} yield f.name
}
}
}
The error:
[error] /Volumes/Home/s/hello/slick-active/hw.scala:57: value === is not a member of scala.slick.lifted.Column[String]
[error] f <- Foos if f.name === "?"
You should NOT import there
import scala.slick.driver._
import scala.slick.driver.JdbcProfile._
import from a particular driver instead, e.g.
import scala.slick.driver.H2Driver._