I am trying to insert few data in apple_reportDB but I get the error as Too many arguments for method in the insert query
import scala.slick.driver.MysqlDriver.simple._
case class AppleReport(quantity:String,price:String,sale_amount:String)
//tables
class Suppliers(tag: Tag)
extends Table[AppleReport](tag, "apple_report") {
def quantity=column[String]("quantity")
def price=column[String]("price")
def sale_amount=column[String]("sale_amount")
def * =(quantity,price,sale_amount) <> (AffiliateReportFields.tupled,AffiliateReportFields.unapply)
}
//declaring interface
val suppliers: TableQuery[Suppliers] = TableQuery[Suppliers]
val db=Database.forURL("jdbc:mysql://localhost:3306/apple_reportDB","root","",null, driver="com.mysql.jdbc.Driver")
db.withSession { implicit session =>
//create table
suppliers.ddl.create
//Insert data
suppliers += ("apple","cow","cat")
}
Your Suppliers table extends Table[AppleReport]. Hence your insert statement expects a single object of case class AppleReport.
However you are calling the method with 3 Strings ("apple","cow","cat") and so the error. Change it to AppleReport("apple","cow","cat") and your code will work
Related
I would like to understand this error:
found : row.type (with underlying type _#TableElementType)
required: _1#TableElementType
Looks like I was very close, but what is this "1" in _1#TableElementType? Can I convert one in the other?
Edit: useful bits of codes for context (Play + Slick):
abstract class GenericDAO[T <: AbstractTable[_]](...) {
def table: TableQuery[T]
def insert(model: T#TableElementType) = db run (table += model)
}
trait TableObject[T <: AbstractTable[_]] {
def rowFromJson(jsObject: JsObject): T#TableElementType
def dao(driver: JdbcProfile, db: Database): GenericDAO[T]
}
// Controller Action with an instance implementing `tableObject` above:
val tableObject = tableObjectFactory("test")
val row = tableObject.rowFromJson(request.body.asJson.get)
val dao = tableObject.dao(driver, db) // tableObject has a DOA extending GenericDAO
dao.insert(row)
Example of tableObject:
object TestTable extends TableObject[Test] {
def dao(driver: JdbcProfile, db: Database) = new TestDAO(driver, db)
def rowFromJson(j: JsObject): TestRow = { TestRow(...) }
class TestDAO(...) extends GenericDAO[Test](driver, db) { ... }
}
I use a factory to get the right one from the url:
object TableObjectFactory {
def tableObjectFactory(name: String) = {
name match {
case "test" => TestTable
case "projects" => ProjectsTable
case "people" => PeopleTable
...
}
}
}
Although it doesn't explain much, it works if I make the DAO parse the request body and insert, instead of producing the row object separately and applying one of the DAO's methods on it.
I got all kinds of similar errors with names such as _$1#TableElementType, _1$u#TableElementType etc., but I think they are compiler aliases for different instances of the same class.
So the solution was to do
val j: JsValue = request.body.asJson.get
val tableObject: TableObject[_] = tableObjectFactory(table)
val dao = tableObject.dao(driver, db)
val res: Future[Int] = dao.insert(j)
where this new insert method now is abstract in GenericDAO, and in the concrete implementations takes a JsValue and parses it, then inserts:
class TestDAO(override val driver: JdbcProfile, override val db: Database) extends GenericDAO[Test](driver, db) {
import this.driver.api._
val table = TableQuery[Test]
//def insert(model: TestRow) = db run (table += model) // NO!
def insert(j: JsValue): Future[Int] = {
val row = TestRow(
(j \ "id").as[Int],
(j \ "name").as[String],
(j \ "value").as[Float],
(j \ "other").as[String]
)
db run (table += row)
}
}
At the same time, it makes Play forms completely useless, which is a good thing anyway.
I am currently learning Play2, Scala and Slick 3.1, and am pretty stuck with the syntax for using insertOrUpdate and wonder if anyone can please help me.
What I want to do is to return the full row when using insertOrUpdate including the auto inc primary key, but I have only managed to return the number of updated/inserted rows.
Here is my table definition:
package models
final case class Report(session_id: Option[Long], session_name: String, tester_name: String, date: String, jira_ref: String,
duration: String, environment: String, notes: Option[String])
trait ReportDBTableDefinitions {
import slick.driver.PostgresDriver.api._
class Reports(tag: Tag) extends Table[Report](tag, "REPORTS") {
def session_id = column[Long]("SESSION_ID", O.PrimaryKey, O.AutoInc)
def session_name = column[String]("SESSION_NAME")
def tester_name = column[String]("TESTER_NAME")
def date = column[String]("DATE")
def jira_ref = column[String]("JIRA_REF")
def duration = column[String]("DURATION")
def environment = column[String]("ENVIRONMENT")
def notes = column[Option[String]]("NOTES")
def * = (session_id.?, session_name, tester_name, date, jira_ref, duration, environment, notes) <> (Report.tupled, Report.unapply)
}
lazy val reportsTable = TableQuery[Reports]
}
Here is the section of my DAO that relates to insertOrUpdate, and it works just fine, but only returns the number of updated/inserted rows:
package models
import com.google.inject.Inject
import play.api.db.slick.DatabaseConfigProvider
import scala.concurrent.Future
class ReportsDAO #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends DAOSlick {
import driver.api._
def save_report(report: Report): Future[Int] = {
dbConfig.db.run(reportsTable.insertOrUpdate(report).transactionally)
}
}
I have tried playing with "returning" but I can't get the syntax I need and keep getting type mismatches e.g. the below doesn't compile (because it's probably completely wrong!)
def save_report(report: Report): Future[Report] = {
dbConfig.db.run(reportsTable.returning(reportsTable).insertOrUpdate(report))
}
Any help appreciated - I'm new to Scala and Slick so apologies if I'm missing something really obvious.
Solved - posting it incase it helps anyone else trying to do something similar:
//will return the new session_id on insert, and None on update
def save_report(report: Report): Future[Option[Long]] = {
val insertQuery = (reportsTable returning reportsTable.map(_.session_id)).insertOrUpdate(report)
dbConfig.db.run(insertQuery)
}
Works well - insertOrUpdate doesn't returning anything it seems on update, so if I need to get the updated data after the update operation I can then run a subsequent query to get the information using the session id.
You cannot return whole Report, first return Id (returning(reportsTable.map(_.session_id))) and then get whole object
Check if report exists in the database if it exists update it, if not go ahead inserting the report into the database.
Note do above operations in all or none fashion by using Transactions
def getReportDBIO(id: Long): DBIO[Report] = reportsTable.filter(_.session_id === id).result.head
def save_report(report: Report): Future[Report] = {
val query = reportsTable.filter(_.session_id === report.session_id)
val existsAction = query.exists.result
val insertOrUpdateAction =
(for {
exists <- existsAction
result <- exists match {
case true =>
query.update(report).flatMap {_ => getReportDBIO(report.session_id)}.transactionally
case false => {
val insertAction = reportsTable.returning(reportsTable.map(_.session_id)) += report
val finalAction = insertAction.flatMap( id => getReportDBIO(id)).transactionally //transactionally is important
finalAction
}
}
} yield result).transactionally
dbConfig.db.run(insertOrUpdateAction)
}
Update your insertOrUpdate function accordingly
You can return the full row, but it is an Option, as the documentation states, it will be empty on an update and will be a Some(...) representing the inserted row on an insert.
So the correct code would be
def save_report(report: Report): Future[Option[Report]] = {dbConfig.db.run(reportsTable.returning(reportsTable).insertOrUpdate(report))}
I'm new to Slick thus I'm not sure whether the problem caused by incorrect usage of implicits or Slick doesn't allow doing what I'm trying to do.
In short I use Slick-pg extension for JSONB support in Postgres. I also use spray-json to deserialize JSONB fields into case classes.
In order to automagically convert columns into objects I wrote generic implicit JsonColumnType that you can see below. It allows me to have any case class for which I defined json formatter to be converted to jsonb field.
On the other hand I want to have alias of JsValue type for the same column so that I can use JSONB-operators.
import com.github.tminglei.slickpg._
import com.github.tminglei.slickpg.json.PgJsonExtensions
import org.bson.types.ObjectId
import slick.ast.BaseTypedType
import slick.jdbc.JdbcType
import spray.json.{JsValue, RootJsonWriter, RootJsonReader}
import scala.reflect.ClassTag
trait MyPostgresDriver extends ExPostgresDriver with PgArraySupport with PgDate2Support with PgRangeSupport with PgHStoreSupport with PgSprayJsonSupport with PgJsonExtensions with PgSearchSupport with PgNetSupport with PgLTreeSupport {
override def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
override val api = MyAPI
private val plainAPI = new API with SprayJsonPlainImplicits
object MyAPI extends API with DateTimeImplicits with JsonImplicits with NetImplicits with LTreeImplicits with RangeImplicits with HStoreImplicits with SearchImplicits with SearchAssistants { //with ArrayImplicits
implicit val ObjectIdColumnType = MappedColumnType.base[ObjectId, Array[Byte]](
{ obj => obj.toByteArray }, { arr => new ObjectId(arr) }
)
implicit def JsonColumnType[T: ClassTag](implicit reader: RootJsonReader[T], writer: RootJsonWriter[T]) = {
val columnType: JdbcType[T] with BaseTypedType[T] = MappedColumnType.base[T, JsValue]({ obj => writer.write(obj) }, { json => reader.read(json) })
columnType
}
}
}
object MyPostgresDriver extends MyPostgresDriver
Here is how my table is defined (minimized version)
case class Article(id : ObjectId, ids : Ids)
case class Ids(doi: Option[String], pmid: Option[Long])
class ArticleRow(tag: Tag) extends Table[Article](tag, "articles") {
def id = column[ObjectId]("id", O.PrimaryKey)
def idsJson = column[JsValue]("ext_ids")
def ids = column[Ids]("ext_ids")
private val fromTuple: ((ObjectId, Ids)) => Article = {
case (id, ids) => Article(id, ids)
}
private val toTuple = (v: Article) => Option((v.id, v.ids))
def * = ProvenShape.proveShapeOf((id, ids) <> (fromTuple, toTuple))(MappedProjection.mappedProjectionShape)
}
private val articles = TableQuery[ArticleRow]
Finally I have function that looks up articles by value of json field
def getArticleByDoi(doi : String): Future[Article] = {
val query = (for (a <- articles if (a.idsJson +>> "doi").asColumnOf[String] === doi) yield a).take(1).result
slickDb.run(query).map { items =>
items.headOption.getOrElse(throw new RuntimeException(s"Article with doi $doi is not found"))
}
}
Sadly I get following exception in runtime
java.lang.ClassCastException: spray.json.JsObject cannot be cast to server.models.db.Ids
The problem is in SpecializedJdbcResultConverter.base where ti.getValue is being called with wrong ti. It should be slick.driver.JdbcTypesComponent$MappedJdbcType but instead it's com.github.tminglei.slickpg.utils.PgCommonJdbcTypes$GenericJdbcType. As result wrong type is passed into my tuple converter.
What makes Slick choose different type for column even though there is explicit definition of projection in table row class ?
Sample project that demonstrates the issue is here.
I'm using scala and slick here, and I have a baserepository which is responsible for doing the basic crud of my classes.
For a design decision, we do have updatedTime and createdTime columns all handled by the application, and not by triggers in database. Both of this fields are joda DataTime instances.
Those fields are defined in two traits called HasUpdatedAt, and HasCreatedAt, for the tables
trait HasCreatedAt {
val createdAt: Option[DateTime]
}
case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt
I would like to know how can I use reflection to call the user copy method, to update the createdAt value during the database insertion method.
Edit after #vptron and #kevin-wright comments
I have a repo like this
trait BaseRepo[ID, R] {
def insert(r: R)(implicit session: Session): ID
}
I want to implement the insert just once, and there I want to createdAt to be updated, that's why I'm not using the copy method, otherwise I need to implement it everywhere I use the createdAt column.
This question was answered here to help other with this kind of problem.
I end up using this code to execute the copy method of my case classes using scala reflection.
import reflect._
import scala.reflect.runtime.universe._
import scala.reflect.runtime._
class Empty
val mirror = universe.runtimeMirror(getClass.getClassLoader)
// paramName is the parameter that I want to replacte the value
// paramValue is the new parameter value
def updateParam[R : ClassTag](r: R, paramName: String, paramValue: Any): R = {
val instanceMirror = mirror.reflect(r)
val decl = instanceMirror.symbol.asType.toType
val members = decl.members.map(method => transformMethod(method, paramName, paramValue, instanceMirror)).filter {
case _: Empty => false
case _ => true
}.toArray.reverse
val copyMethod = decl.declaration(newTermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
copyMethodInstance(members: _*).asInstanceOf[R]
}
def transformMethod(method: Symbol, paramName: String, paramValue: Any, instanceMirror: InstanceMirror) = {
val term = method.asTerm
if (term.isAccessor) {
if (term.name.toString == paramName) {
paramValue
} else instanceMirror.reflectField(term).get
} else new Empty
}
With this I can execute the copy method of my case classes, replacing a determined field value.
As comments have said, don't change a val using reflection. Would you that with a java final variable? It makes your code do really unexpected things. If you need to change the value of a val, don't use a val, use a var.
trait HasCreatedAt {
var createdAt: Option[DateTime] = None
}
case class User(name:String) extends HasCreatedAt
Although having a var in a case class may bring some unexpected behavior e.g. copy would not work as expected. This may lead to preferring not using a case class for this.
Another approach would be to make the insert method return an updated copy of the case class, e.g.:
trait HasCreatedAt {
val createdAt: Option[DateTime]
def withCreatedAt(dt:DateTime):this.type
}
case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt {
def withCreatedAt(dt:DateTime) = this.copy(createdAt = Some(dt))
}
trait BaseRepo[ID, R <: HasCreatedAt] {
def insert(r: R)(implicit session: Session): (ID, R) = {
val id = ???//insert into db
(id, r.withCreatedAt(??? /*now*/))
}
}
EDIT:
Since I didn't answer your original question and you may know what you are doing I am adding a way to do this.
import scala.reflect.runtime.universe._
val user = User("aaa", None)
val m = runtimeMirror(getClass.getClassLoader)
val im = m.reflect(user)
val decl = im.symbol.asType.toType.declaration("createdAt":TermName).asTerm
val fm = im.reflectField(decl)
fm.set(??? /*now*/)
But again, please don't do this. Read this stackoveflow answer to get some insight into what it can cause (vals map to final fields).
Consider the Favorites table object below, we want to write a query to find Favorites by their type (defined below). We have also defined a Typemapper, to map a FavoriteType to a String for the database
import scala.slick.driver.PostgresDriver.simple._
//Other imports have been omitted in this question
object Favorites extends Table[Favorite]("favorites") {
// Convert the favoriteTypes to strings for the database
implicit val favoriteMapping: TypeMapper[FavorietType] = MappedTypeMapper.base[FavorietType, String](
favType => FavorietType.values.find(_ == favType).get.mapping,
mapping => FavorietType.values.find(_.mapping == mapping).get
)
def favoriteType = column[FavoriteType]("type")
//other columns here
This is the query I want to write (however it does not compile)
def queryByFavoriteType(ftype : FavoriteType)(implicit s: Session) = {
for(
f <- Favorieten if f.favoriteType === ftype
) yield f
}
}
Here I have defined de different FavoriteType objects (this is outside the Favorieten Object)
sealed case class FavorietType(mapping: String) {
override def toString = mapping.capitalize
}
object FavoriteType {
object Exam extends FavoriteType("examen")
object Topic extends FavoriteType("onderwerp")
object Paper extends FavoriteType("profielwerkstuk")
val values = Seq(Exam , Topic , Paper )
}
The problem I have here is that the query does not compile:
value === is not a member of scala.slick.lifted.Column[models.gebruiker.FavorietType]
It appears that === can not be used to compare a User-defined type, is this true? Is there an alternative way to do this?
Edit
Related issue: before I had my TypeMapper without explicit type, it was defined as implicit val favoriteMapping = MappedTypeMapper.base[FavorietType, String]( ...
When I would write a query that would compare a FavoriteType.Exam (for example) such as
def queryByFavoriteExam()(implicit s: Session) = {
for(f <- Favorieten if f.favorietType === FavorietType.Exam) yield f
}
This would result in the error could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[models.gebruiker.FavorietType.Exam.type]
The solution for this is the same as the one presented below
When in doubt with Slick, go check out the unit tests. After reading their docs on mapping in a custom type and then looking at their unit tests, I got your query code to compile by changing it to:
def queryByFavoriteType(ftype : FavoriteType)(implicit s: Session) = {
for(f <- Favorites if f.favoriteType === (ftype:FavoriteType)) yield f
}
Also, I had imported the H2Driver just to get things to compile (import scala.slick.driver.H2Driver.simple._). I was assuming that you also had imported whatever driver it is that you need for your db.
EDIT
My full code example is as follows:
import scala.slick.driver.PostgresDriver.simple._
import scala.slick.session.Session
sealed case class FavoriteType(mapping: String) {
override def toString = mapping.capitalize
}
case class Favorite(ft:FavoriteType, foo:String)
object FavoriteType {
object Exam extends FavoriteType("examen")
object Topic extends FavoriteType("onderwerp")
object Paper extends FavoriteType("profielwerkstuk")
val values = Seq(Exam , Topic , Paper )
}
object Favorites extends Table[Favorite]("favorites") {
// Convert the favoriteTypes to strings for the database
implicit val favoriteMapping = MappedTypeMapper.base[FavoriteType, String](
{favType => FavoriteType.values.find(_ == favType).get.mapping},
{mapping => FavoriteType.values.find(_.mapping == mapping).get}
)
def favoriteType = column[FavoriteType]("type")
def foo = column[String]("foo")
def * = favoriteType ~ foo <> (Favorite.apply _, Favorite.unapply _)
def queryByFavoriteType(ftype : FavoriteType)(implicit s: Session) = {
for(f <- Favorites if f.favoriteType === (ftype:FavoriteType)) yield f
}
}