2

I'm trying to write a simple Active Record style DAO in Slick and I'm finding it very hard going. I'd like to insert a row using the case class representation, set its "updated" column using database time and return the new row values as a case class.

i.e. the DAO should look like this:

class FooDao {
  def db: JdbcBackend.Database

  def insert(foo: FooRow): Future[FooRow] = {
    ???
  }
}

// This class is auto-generated by slick codegen
case class FooRow(
  id: Int,
  created: Option[java.sql.Timestamp]
  updated: Option[java.sql.Timestamp],
  x1: String,
  x2: String,
  x3: String)

I want the returned FooRow object to have the correct ID and created timestamp that was assigned by the database.

I'll also want an update method that updates a FooRow, using the database clock for the updated column and returning a new FooRow

Things I have tried:

Using "returning"

I can insert the case class and get the generated id using the returning helper as described at Slick 3.0 Insert and then get Auto Increment Value, i.e.

class FooDao {
  def db: JdbcBackend.Database

  def insert(foo: FooRow): Future[FooRow] = {

    db.run((Foos returning Foos.map(_.id)) += fooRow)
        .map(id => fooRow.copy(id = id))
  }
}

This works but I can't see any way to use CURRENT_TIMESTAMP() in the insert statement to get the updated column filled in. The types just don't allow it.

Using sqlu

I can write the SQL that I'm aiming for using sqlu, but then I can't see any way to get the generated key out of Slick. The JDBC API allows it via getGeneratedKeys() but I can't see how to access it here:

class FooDao {
  def db: JdbcBackend.Database

  def insert(foo: FooRow): Future[FooRow] = {

    val sql: DBIO[Int] = sqlu"""
INSERT INTO  foos
            (created,
             updated,
             x1,
             x2,
             x3)
     VALUES (current_timestamp(),
             current_timestamp(),
             $foo.x1,
             $foo.x2,
             $foo.x3)
               """

    db.run(sql)
        .map(rowCount => ???)
  }
}

Using active-slick

The https://github.com/strongtyped/active-slick project looks like nearly what I need, but it doesn't appear to support updated/created columns.

Using database default values / triggers

I suppose I could implement the created column using a database default value, but how would I prevent Slick from passing in an explicit value in its insert statement and overruling that?

I suppose I could implement the updated column using a database trigger, but I'd prefer to keep my logic in the Scala layer and keep the database simple.

Using the web-server clock instead of the DB clock

The closest I can get to this at the moment is to use the web-server clock instead of the DB clock:

class FooDao {
  def db: JdbcBackend.Database

  def insert(foo: FooRow): Future[FooRow] = {

    val now = Some(Timestamp.from(Instant.now()))

    val toInsert = fooRow.copy(
       created = now,
       updated = now)

    db.run((Foos returning Foos.map(_.id)) += toInsert)
        .map(id => toInsert.copy(id = id))
  }
}

However, I'd much prefer to use the database clock for these columns, in case different web servers have different times.

Any ideas would be gratefully received. I'm strongly considering abandoning Slick and using JOOQ, where this would be easy.

Community
  • 1
  • 1
Rich
  • 15,048
  • 2
  • 66
  • 119
  • Why is there a tag with `activerecord` ? And its just my opinion... Slick and Scala are biased towards `functional paradigm` and something like ActiveRecord is not going to be very useful to most Scala programmers. Different paradigm calls for different approaches. Even the best recipe for making a Pizza should not be applied for making Burgers. – sarveshseri Oct 27 '16 at 07:34
  • And if you are looking for a way to support arbitrary models like ActiveRecord does... you will need to use both scala-macros and scala-reflection. – sarveshseri Oct 27 '16 at 07:40
  • By ActiveRecord, I meant only https://en.wikipedia.org/wiki/Active_record_pattern , i.e. "The interface of an object conforming to this pattern would include functions such as Insert, Update, and Delete, plus properties that correspond more or less directly to the columns in the underlying database table.". I don't think there's anything non-functional about that. If you have a "pizza" recipe that would be more appropriate, I'm all ears. – Rich Oct 27 '16 at 08:08
  • I don't know what you mean by "arbitrary models" here. The Slick code-gen creates case classes like "FooRow" from the DB schema before compile time. No reflection or macros are required. – Rich Oct 27 '16 at 08:08
  • Yes... Slick-code-gen does that. But is that a correct approach to take for an application that you are developing from scrach ? Shouldn't that be other way around (you code controlling the DB instead of DB controlling the code) ? – sarveshseri Oct 27 '16 at 08:16
  • I am not developing the application from scratch, but connecting to an existing database. Whether the database schema is code-first or schema-first is completely irrelevant here. – Rich Oct 27 '16 at 09:05
  • 1
    For timestamp default - `def created = column[Timestamp]("created", SqlType("timestamp not null default CURRENT_TIMESTAMP"))` and for updated `def updated = column[Timestamp]("updated", SqlType("timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"))`. That will be simple to do if you had manually defined the embedding for your models. But I believe you can still achieve this by overriding the default behaviour of slick-code-gen for timestamp fields named as `created` or `updated`. – sarveshseri Oct 27 '16 at 09:13
  • And if you want to enforce that your created column is non-updatable... as far as I know the only choice will be DB-triggers. – sarveshseri Oct 27 '16 at 09:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126806/discussion-between-rich-and-sarvesh-kumar-singh). – Rich Oct 27 '16 at 09:32

0 Answers0