3

I'm trying to get a hang of Slick by doing a small test. I'm trying to do an insert. The test runs, no errors, but when I check the db, no record has been inserted.

What am I doing wrong?

Here's my test code:

Note: I disabled the first 'flatMap' because when I wanted to test the second insert method, and that code was not executed when the first flatmap function was enabled.

Both insert methods do not insert a new record. The first query for all items does work. The 'Test id:xx' lines are printed to console.

object TestSlick extends App {

  import slick.driver.PostgresDriver.api._
  import concurrent.ExecutionContext.Implicits.global
  import concurrent.duration._

  val config = ConfigFactory.load()
  val username = config.getString("app.database.jdbc.username")
  val password = config.getString("app.database.jdbc.password")
  val url: String = config.getString("app.database.jdbc.url")

  val db = Database.forURL(url, username, password)

  try {
    import Tables._

    val res = db.run(headlines.result).map(_.foreach {
      case HeadLineRow(id, _, _, _, _, companyId, text, from, days, end, user) =>
        println(s"Test id:$id")
    }).flatMap { _ =>
//      println("Inserting....")
//      val ts = Timestamp.valueOf(LocalDateTime.now())
//      val insertAction: DBIO[Option[Int]] = (headlines returning headlines.map(_.id)) +=
//        HeadLineRow(None, 100, 100, "tekst", ts, 5, ts, None, None, None, None)
//
//      db.run(insertAction.transactionally.map(
//        newId => println(s"New id: $newId"))
//      )
//    }.flatMap { _ =>
      println("Inserting....(2)")
      val ts = Timestamp.valueOf(LocalDateTime.now())
      val insertAction = headlines.map(p => p) += HeadLineRow(None, 1921, 65, "tekst2", ts, 5, ts, None, None, None, None)

      db.run(insertAction.transactionally.map(
        r => println(s"Insert result: ${r}"))
      )
    }

    Await.ready(res, 30 seconds);

  } finally db.close()
}

And my table (generated using Slick's generator and then adjusted a bit (auto-inc id, swapped some properties around))

package com.wanneerwerkik.db.slick

// AUTO-GENERATED Slick data model
/** Stand-alone Slick data model for immediate use */
object Tables extends {
  val profile = slick.driver.PostgresDriver
} with Tables

/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */
trait Tables {
  val profile: slick.driver.JdbcProfile
  import profile.api._
  import slick.model.ForeignKeyAction
  import slick.collection.heterogeneous._
  import slick.collection.heterogeneous.syntax._
  // NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns.
  import slick.jdbc.{GetResult => GR}

  /** DDL for all tables. Call .create to execute. */
  lazy val schema = Array(headlines.schema).reduceLeft(_ ++ _)
  @deprecated("Use .schema instead of .ddl", "3.0")
  def ddl = schema

  /**
   * Entity class storing rows of table 'head_line_bar'
   *  @param id Database column id SqlType(int4), PrimaryKey
   *  @param createdBy Database column created_by SqlType(int4), Default(None)
   *  @param createdOn Database column created_on SqlType(timestamp), Default(None)
   *  @param updatedBy Database column updated_by SqlType(int4), Default(None)
   *  @param updatedOn Database column updated_on SqlType(timestamp), Default(None)
   *  @param companyId Database column company_id SqlType(int4), Default(None)
   *  @param contentType Database column content_type SqlType(varchar), Length(255,true), Default(None)
   *  @param fromDate Database column from_date SqlType(timestamp), Default(None)
   *  @param numberofdays Database column numberofdays SqlType(int4), Default(None)
   *  @param uptoEndDate Database column upto_end_date SqlType(timestamp), Default(None)
   *  @param userId Database column user_id SqlType(int4), Default(None)
   */
  case class HeadLineRow(
      id: Option[Int],
      userId: Int,
      companyId: Int,
      contentType: String,
      fromDate: java.sql.Timestamp,
      numberofdays: Int,
      uptoEndDate: java.sql.Timestamp,
      createdBy: Option[Int] = None,
      createdOn: Option[java.sql.Timestamp] = None,
      updatedBy: Option[Int] = None,
      updatedOn: Option[java.sql.Timestamp] = None
  )

  /** GetResult implicit for fetching HeadLineBarRow objects using plain SQL queries */
  implicit def GetResultHeadLineRow(implicit e0: GR[Int], e1: GR[Option[Int]], e2: GR[Option[java.sql.Timestamp]], e3: GR[Option[String]]): GR[HeadLineRow] = GR{
    prs => import prs._
    HeadLineRow.tupled((<<?[Int], <<[Int], <<[Int], <<[String], <<[java.sql.Timestamp], <<[Int], <<[java.sql.Timestamp], <<?[Int], <<?[java.sql.Timestamp], <<?[Int], <<?[java.sql.Timestamp]))
  }
  /**
   * Table description of table head_line_bar.
   * Objects of this class serve as prototypes for rows in queries.
   */
  class Headlines(_tableTag: Tag) extends Table[HeadLineRow](_tableTag, "head_line_bar") {
    def * = (id, userId, companyId, contentType, fromDate, numberofdays, uptoEndDate, createdBy, createdOn, updatedBy, updatedOn) <> (HeadLineRow.tupled, HeadLineRow.unapply)
    /** Maps whole row to an option. Useful for outer joins. */
    def ? = (Rep.Some(id), userId, companyId, contentType, fromDate, numberofdays, uptoEndDate, createdBy, createdOn, updatedBy, updatedOn).shaped.<>({r=>import r._; _1.map(_=> HeadLineRow.tupled((_1.get, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))

    /** Database column id SqlType(int4), PrimaryKey */
    val id: Rep[Option[Int]] = column[Option[Int]]("id", O.PrimaryKey, O.AutoInc)
    /** Database column user_id SqlType(int4), Default(None) */
    val userId: Rep[Int] = column[Int]("user_id")
    /** Database column company_id SqlType(int4), Default(None) */
    val companyId: Rep[Int] = column[Int]("company_id")
    /** Database column content_type SqlType(varchar), Length(255,true), Default(None) */
    val contentType: Rep[String] = column[String]("content_type", O.Length(255,varying=true))
    /** Database column from_date SqlType(timestamp), Default(None) */
    val fromDate: Rep[java.sql.Timestamp] = column[java.sql.Timestamp]("from_date")
    /** Database column numberofdays SqlType(int4), Default(None) */
    val numberofdays: Rep[Int] = column[Int]("numberofdays")
    /** Database column upto_end_date SqlType(timestamp), Default(None) */
    val uptoEndDate: Rep[java.sql.Timestamp] = column[java.sql.Timestamp]("upto_end_date")
    /** Database column created_by SqlType(int4), Default(None) */
    val createdBy: Rep[Option[Int]] = column[Option[Int]]("created_by", O.Default(None))
    /** Database column created_on SqlType(timestamp), Default(None) */
    val createdOn: Rep[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("created_on", O.Default(None))
    /** Database column updated_by SqlType(int4), Default(None) */
    val updatedBy: Rep[Option[Int]] = column[Option[Int]]("updated_by", O.Default(None))
    /** Database column updated_on SqlType(timestamp), Default(None) */
    val updatedOn: Rep[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("updated_on", O.Default(None))
  }
  /** Collection-like TableQuery object for table HeadLineBar */
  lazy val headlines = new TableQuery(tag => new Headlines(tag))

}

Log output is too big to paste here so I put it in this gist.

As suggested I added a readLine to wait for the result, but it was already output the same stuff. I also added a completion handler on the Future to print it's Success or Failure. Apparently it fails with a RejectedExecutionException. Why?

Failure: java.util.concurrent.RejectedExecutionException: Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@2e4db0df rejected from java.util.concurrent.ThreadPoolExecutor@43760a50[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1]
User
  • 31,811
  • 40
  • 131
  • 232
Joost den Boer
  • 4,556
  • 4
  • 25
  • 39
  • Is it printing anything at all? Otherwise I suspect that the program terminates more or less immediately. Try do a `io.StdIn.readLine()` as the last line, requiring the App to wait for an enter press before exiting. – Rikard Jul 31 '15 at 20:34
  • It was already printing loads of stuff. I added the 'readLine' as suggested and also added a completion handler on the Future so I can see the result. I added the output to the question. Apparently the Future fails with a _RejectedExecutionException_. Why? – Joost den Boer Aug 01 '15 at 07:33
  • It looks like the internal pool has been shutdown (it says: [Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1]). So I assume that db.close() has been called before the insert action could be performed. Try putting the io.StdIn.readLine() just after Await.ready(). Maybe also add some `onFailure()` error handlers on the resulting futures. – Rikard Aug 01 '15 at 09:01
  • Did you try out things, like doing exactly the same query you do in step 1 again? Does it only fail on insert? Or comment `finally db.close()` (this shouldn't be related but who knows), also did you try executing this code outside of a test? Sometimes testing frameworks have problems with asynchronous code (even if you use await)... – User Aug 01 '15 at 20:58

1 Answers1

0

This is just a guess, but maybe your testing framework is somehow confused by the fact that flatMap internally spawns a new task, requiring again the execution context (see e.g. this thread - it's about Scala 2.10 but I think this hasn't changed). So the resources are freed before your insert executes.

Have you tried putting a println in the finally block, to see if this is called before or after the message accompanying the insert?

Have you tried running both futures synchronously, using await? Likely you will not get this issue in this case.

You may consider testing with full asynchronous support, see e.g. this link.

Community
  • 1
  • 1
User
  • 31,811
  • 40
  • 131
  • 232
  • Oh damn. Of course the 'finally' block gets called before the Future completes since the result of the Try is the Future. And therefore the connection was already closed before the insert could take place. – Joost den Boer Aug 03 '15 at 07:49