Slick 2: Delete row in slick with play framework - scala

This requirement should be really easy, but I don't know why is not working. I want to delete a row based on it's id using slick with play framework.
I'm following this example from play-slick module, but compiler complains that value delete is not a member of scala.slick.lifted.Query[models.Tables.MyEntity,models.Tables.MyEntity#TableElementType].
My controller looks like:
def delete(id: Int) = DBAction{ implicit rs =>
val q = MyEntity.where(_.id === id)
q.delete
Ok("Entity deleted")
}
I've imported the play.api.db.slick.Config.driver.simple._
What am I doing wrong?
Edit:
My schema definition looks like:
class Cities(tag: Tag) extends Table[CityRow](tag, "cities") {
def * = (cod, name, state, lat, long, id) <> (CityRow.tupled, CityRow.unapply)
/** Maps whole row to an option. Useful for outer joins. */
def ? = (cod.?, name.?, state.?, lat, long, id.?).shaped.<>({r=>import r._; _1.map(_=> CityRow.tupled((_1.get, _2.get, _3.get, _4, _5, _6.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
val cod: Column[String] = column[String]("cod")
val name: Column[String] = column[String]("name")
val state: Column[Int] = column[Int]("state")
val lat: Column[Option[String]] = column[Option[String]]("lat")
val long: Column[Option[String]] = column[Option[String]]("long")
val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
/** Foreign key referencing Departamentos (database name fk_ciudades_departamentos1) */
lazy val stateFk = foreignKey("fk_cities_states", state, States)(r => r.idstate, onUpdate=ForeignKeyAction.NoAction, onDelete=ForeignKeyAction.NoAction)
}

I also had a look at that example some time ago and it looked wrong to me too, not sure wether I was doing something wrong myself or not, the delete function was always a bit tricky to get right, expecially using the lifted.Query (like you are doing). Later in the process I made it work importing the right drivers, in my case scala.slick.driver.PostgresDriver.simple._.
Edit after comment:
Probably you have an error in the shape function, hard to say without looking at your schema declaration. This is an example:
case class CityRow(id: Long, name: String) {
class City(tag: Tag) extends Table[CityRow](tag, "city") {
def * = (id, name) <>(CityRow.tupled, CityRow.unapply)
^this is the shape function.
def ? = (id.?, name).shaped.<>({
r => import r._
_1.map(_ => CityRow.tupled((_1.get, _2)))
}, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
val id: Column[Long] = column[Long]("id", O.AutoInc, O.PrimaryKey)
val name: Column[String] = column[String]("name")
}

Related

Scala Slick: Type Mismatch "String vs Tables.Row" during insert

Schema was generated automatically from PostgreSQl-database:
class Geopointtable(_tableTag: Tag) extends profile.api.Table[GeopointtableRow](_tableTag, "geopointtable") {
def * = (id, latitude, longitude, date, time) <> (GeopointtableRow.tupled, GeopointtableRow.unapply)
def ? = ((Rep.Some(id), Rep.Some(latitude), Rep.Some(longitude), Rep.Some(date), Rep.Some(time))).shaped.<>({r=>import r._; _1.map(_=> GeopointtableRow.tupled((_1.get, _2.get, _3.get, _4.get, _5.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
val id: Rep[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
val latitude: Rep[Int] = column[Int]("latitude")
val longitude: Rep[Int] = column[Int]("longitude")
val date: Rep[java.sql.Date] = column[java.sql.Date]("date")
val time: Rep[java.sql.Time] = column[java.sql.Time]("time")
}
lazy val Geopointtable = new TableQuery(tag => new Geopointtable(tag))
}
I'm trying to insert some values in table:
val db = Database.forConfig("geodb")
val date = java.sql.Date.valueOf(LocalDateTime.now().toLocalDate.toString)
val time = java.sql.Time(LocalDateTime.now().toLocalTime)
var point = TableQuery[Tables.Geopointtable]
Await.result(
db.run(
point += Tables.GeopointtableRow(1, 2, 3, date, time))
, Duration.Inf)
But for line "Tables.GeopointtableRow(1, 2, 3, date, time))" compiler specify on 2 type mismatches:
Required String, Found Tables.GeopointtableRow".
Required DBIOAction[NotInferedR, NoStream, Nothing], Found Unit
You need to tell Slick which table to insert the record into. You probably want something like
db.run(
Tables.Geopointtable += Tables.GeopointtableRow(1, 2, 3, date, time)
)
The problem is solved.
I only import in my class following dependencies: "Tables." and "Tables.profile.api.".
So now I apply to generated table not by "Tables.Geopointtable", but directly - "Geopointtable".
I wonder, why this import was not suggested to add by my IDEA, of cause. ))

Scala Slick extend the functionality of a table outside of its structure

I'm using Slick code-gen to output a very normal Tables.scala file, which maps the tables/columns of the structure of my database.
However i want to EXTEND the functionality of those tables in my DAOs and its proving to be impossible for me (roughly new to scala and play framework)
INSIDE Tables.scala INSIDE the class Meeting
you can write functions that have access to the columns I.E
class Meeting(_tableTag: Tag) extends profile.api.Table[MeetingRow](_tableTag, "meeting") {
def * = (id, dateandtime, endtime, organisationid, details, adminid, datecreated, title, agenda, meetingroom) <> (MeetingRow.tupled, MeetingRow.unapply)
def ? = (Rep.Some(id), Rep.Some(dateandtime), Rep.Some(endtime), Rep.Some(organisationid), Rep.Some(details), Rep.Some(adminid), Rep.Some(datecreated), Rep.Some(title), Rep.Some(agenda), Rep.Some(meetingroom)).shaped.<>({r=>import r._; _1.map(_=> MeetingRow.tupled((_1.get, _2.get, _3.get, _4.get, _5.get, _6.get, _7.get, _8.get, _9.get, _10.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
val id: Rep[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
val dateandtime: Rep[java.sql.Timestamp] = column[java.sql.Timestamp]("dateandtime")
val endtime: Rep[java.sql.Timestamp] = column[java.sql.Timestamp]("endtime")
val organisationid: Rep[Int] = column[Int]("organisationid")
val details: Rep[String] = column[String]("details", O.Default(""))
val adminid: Rep[Int] = column[Int]("adminid")
val datecreated: Rep[java.sql.Timestamp] = column[java.sql.Timestamp]("datecreated")
val title: Rep[String] = column[String]("title", O.Default(""))
val agenda: Rep[String] = column[String]("agenda", O.Default(""))
val meetingroom: Rep[Int] = column[Int]("meetingroom")
def getAttendees = Tables.Meeting2uzer.filter(_.meetingid === id)
where "ID" in the above function is a column in Meeting.
now the problem arises when i want to write that same function "getAttendees" in my DAO which doesn't have access to the columns in scope.
something along the lines of....
#Singleton
class SlickMeetingDAO #Inject()(db: Database)(implicit ec: ExecutionContext) extends MeetingDAO with Tables {
override val profile: JdbcProfile = _root_.slick.jdbc.PostgresProfile
import profile.api._
private val queryById = Compiled((id: Rep[Int]) => Meeting.filter(_.id === id))
def getAttendees = Meeting2uzer.filter(_.meetingid === "NEED ID OF COLUMN IN SCOPE")
How do i get 'id' which is a column in Tables.Meeting in scope in my DAO to finish this getAttendees function.
If I understand correctly, you're trying to join two tables?
Meeting2uzer.join(Meeting).on(_.meetingid === _.id)
What you've done inside the Tables.scala, would be more inline with creating a foreign key in Slick. You could create a slick foreign key, instead of using an explicit join. See the documentation for Slick here.

Playframework Scala play-slick3-example project: JdbcSQLException: Column "TEST" not found

I downloaded a starter application with Play Framework 2.5 and Slick 3.1, git here.
When I add a simple to column named "test" to Project.scala. I get this error:
[JdbcSQLException: Column "TEST" not found; SQL statement:
select "ID", "NAME", "TEST" from "PROJECT" [42122-187]]
I just change the Project case class by adding the argument test and ProjectsTable class with functions test, * and ?:
case class Project(id: Long, name: String, test: String)
private class ProjectsTable(tag: Tag) extends Table[Project](tag, "PROJECT") {
def id = column[Long]("ID", O.AutoInc, O.PrimaryKey)
def name = column[String]("NAME")
def test = column[String]("TEST")
def * = (id, name, test) <> (Project.tupled, Project.unapply)
def ? = (id.?, name.?, test.?).shaped.<>({ r => import r._; _1.map(_ => Project.tupled((_1.get, _2.get, _3.get))) }, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
}
And the function: create
def create(name: String): Future[Long] = {
val project = Project(0, name, "d")
db.run(Projects returning Projects.map(_.id) += project)
}
Thank you very much for your help!
By adding the field test to the ProjectsTable model you are telling Slick that the database table PROJECT has a column named test - but the table PROJECT does not have a column named test.
You can see the SQL schema here: https://github.com/nemoo/play-slick3-example/blob/6c122970d7506bedb9230e3a31c30a5c7e27e93b/conf/evolutions/default/1.sql

Slick insert into postgreSQL

I am just trying to add a row from slick to my postgreSQL Database.
Here what I am trying to do :
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
import dbConfig.driver.api._
val query = Task += new TaskRow(5, "taskName", status = "other")
println(Task.insertStatement)
val resultingQuery = dbConfig.db.run(query).map(res => "Task successfully added").recover {
case ex: Exception => ex.getCause.getMessage
}
Here the result of println :
insert into "task" ("taskname","description","status","datetask","pediocitynumber","periodicitytype") values (?,?,?,?,?,?)
I don't have any result exception or success from the resulting query.
Code generate by slick-codegen 3.1.1 :
case class TaskRow(taskid: Int, taskname: String, description: Option[String] = None, status: String, datetask: Option[java.sql.Timestamp] = None, pediocitynumber: Option[Int] = None, periodicitytype: Option[String] = None)
/** GetResult implicit for fetching TaskRow objects using plain SQL queries */
implicit def GetResultTaskRow(implicit e0: GR[Int], e1: GR[String], e2: GR[Option[String]], e3: GR[Option[java.sql.Timestamp]], e4: GR[Option[Int]]): GR[TaskRow] = GR{
prs => import prs._
TaskRow.tupled((<<[Int], <<[String], <<?[String], <<[String], <<?[java.sql.Timestamp], <<?[Int], <<?[String]))
}
/** Table description of table task. Objects of this class serve as prototypes for rows in queries. */
class Task(_tableTag: Tag) extends Table[TaskRow](_tableTag, "task") {
def * = (taskid, taskname, description, status, datetask, pediocitynumber, periodicitytype) <> (TaskRow.tupled, TaskRow.unapply)
/** Maps whole row to an option. Useful for outer joins. */
def ? = (Rep.Some(taskid), Rep.Some(taskname), description, Rep.Some(status), datetask, pediocitynumber, periodicitytype).shaped.<>({r=>import r._; _1.map(_=> TaskRow.tupled((_1.get, _2.get, _3, _4.get, _5, _6, _7)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
/** Database column taskid SqlType(serial), AutoInc, PrimaryKey */
val taskid: Rep[Int] = column[Int]("taskid", O.AutoInc, O.PrimaryKey)
/** Database column taskname SqlType(text) */
val taskname: Rep[String] = column[String]("taskname")
/** Database column description SqlType(text), Default(None) */
val description: Rep[Option[String]] = column[Option[String]]("description", O.Default(None))
/** Database column status SqlType(statustask) */
val status: Rep[String] = column[String]("status")
/** Database column datetask SqlType(timestamp), Default(None) */
val datetask: Rep[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("datetask", O.Default(None))
/** Database column pediocitynumber SqlType(int4), Default(None) */
val pediocitynumber: Rep[Option[Int]] = column[Option[Int]]("pediocitynumber", O.Default(None))
/** Database column periodicitytype SqlType(periodicitytype), Default(None) */
val periodicitytype: Rep[Option[String]] = column[Option[String]]("periodicitytype", O.Default(None))
}
/** Collection-like TableQuery object for table Task */
lazy val Task = new TableQuery(tag => new Task(tag))
Could someone explain what I am doing wrong?
EDIT:
To clarify my question :
The row is not added to the table, it seems that nothing happen. I can't see any exception or error thrown.
I think it could come from the sql statement and the questions marks (values (?,?,?,?,?,?)). It should be the actual values of the fields, right?
More code:
class Application #Inject()(dbConfigProvider: DatabaseConfigProvider) extends Controller {
def index = Action {
Ok(views.html.main())
}
def taskSave = Action.async { implicit request =>
println(request.body.asJson)
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
import dbConfig.driver.api._
val query = Task += new TaskRow(5, "taskName", status = "other")
println(Task.insertStatement)
println(query)
val resultingQuery = dbConfig.db.run(query).map(res => "TAsk successfully added").recover {
case ex: Exception => ex.getCause.getMessage
}
resultingQuery.map(r => println("result : " + r))
Future(Ok(""))
}
}
route :
PUT /task-save controllers.Application.taskSave
The statement is not wrong because of the values (?,?,? ...).
The lazy val Task is of type TableQuery[...] . When you call insertStatement on this object you get back a string that represents the template SQL string that runs in the background. That is expected.
So that is not the indicator of the problem.
Try something like this :
val db = Database.forConfig("h2mem1")
try {
Await.result(db.run(DBIO.seq(
// create the schema
Task.schema.create,
// insert two User instances
Task += (2, "abcd", "201-01-01"),
// print the users (select * from USERS)
Task.result.map(println))), Duration.Inf)
} finally db.close

Play Framework and Slick: testing database-related services

I'm trying to follow the most idiomatic way to having a few fully tested DAO services.
I've got a few case classes such as the following:
case class Person (
id :Int,
firstName :String,
lastName :String
)
case class Car (
id :Int,
brand :String,
model :String
)
Then I've got a simple DAO class like this:
class ADao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
private val persons = TableQuery[PersonTable]
private val cars = TableQuery[CarTable]
private val personCar = TableQuery[PersonCar]
class PersonTable(tag: Tag) extends Table[Person](tag, "person") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("name")
def lastName = column[String]("description")
def * = (id, firstName, lastName) <> (Person.tupled, Person.unapply)
}
class CarTable(tag: Tag) extends Table[Car](tag, "car") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def brand = column[String]("brand")
def model = column[String]("model")
def * = (id, brand, model) <> (Car.tupled, Car.unapply)
}
// relationship
class PersonCar(tag: Tag) extends Table[(Int, Int)](tag, "person_car") {
def carId = column[Int]("c_id")
def personId = column[Int]("p_id")
def * = (carId, personId)
}
// simple function that I want to test
def getAll(): Future[Seq[((Person, (Int, Int)), Car)]] = db.run(
persons
.join(personCar).on(_.id === _.personId)
.join(cars).on(_._2.carId === _.id)
.result
)
}
And my application.conf looks like:
slick.dbs.default.driver="slick.driver.PostgresDriver$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://super-secrete-prod-host/my-awesome-db"
slick.dbs.default.db.user="myself"
slick.dbs.default.db.password="yolo"
Now by going through Testing with databases and trying to mimic play-slick sample project
I'm getting into so much trouble and I cannot seem to understand how to make my test use a different database (I suppose I need to add a different db on my conf file, say slick.dbs.test) but then I couldn't find out how to inject that inside the test.
Also, on the sample repo, there's some "magic" like Application.instanceCache[CatDAO] or app2dao(app).
Can anybody point me at some full fledged example of or repo that deals correctly with testing play and slick?
Thanks.
I agree it's confusing. I don't know if this is the best solution, but I ended up having a separate configuration file test.conf that specifies an in-memory database:
slick.dbs {
default {
driver = "slick.driver.H2Driver$"
db.driver = "org.h2.Driver"
db.url = "jdbc:h2:mem:play-test"
}
}
and then told sbt to use this when running the tests:
[..] javaOptions in Test ++= Seq("-Dconfig.file=conf/test.conf")