Skip to content

Instantly share code, notes, and snippets.

@ericdoi
Last active October 13, 2016 10:09
Show Gist options
  • Select an option

  • Save ericdoi/3683e00a7c28893e09341cafde2fccd8 to your computer and use it in GitHub Desktop.

Select an option

Save ericdoi/3683e00a7c28893e09341cafde2fccd8 to your computer and use it in GitHub Desktop.
Human Error with Slick Date Comparison

I was having trouble comparing dates in Scala Slick. It turned out to be human error.

There are a lot of posts on Stack Overflow about the difficulty of comparing date types (such as Joda.date.DateTime) in Scala Slick. However, I was using Java.sql.Date, which should be supported directly by Slick, so these discussions turned out to be red herrings.

The upshot is that there is a big difference between these two lines:

      query1.filter(test => test.startDate <= dt && test.endDate >= dt) // OK
      query1.filter(test => dt >= test.startDate && dt <= test.endDate) // NOPE

In the first, test.startDate is a Date wrapped in Slick's Rep container, which has the inequality operators defined. Then dt gets implicitly lifted into a Rep[Date] as well. However, in the second, because the order was reversed, Scala attempts to find the inequality operators on dt which is a plain Date, doesn't find them, and returns an error.

This will happen with any kind of type, not just Dates. However, the reason it came up with Dates was because I was trying to reorder the comparisons to be more intuitive.

The full file for reference:

package models

import java.sql.Date

import model.DbConfig
import slick.driver.MySQLDriver.api._

import scala.concurrent.Future

/**
  * ericdoi 2016-10-13
  */
case class Test(testId: Int, siteId: Int, modelType: ModelTypeEnum, startDate: Date, endDate: Date)

class TestTableDef(tag: Tag) extends Table[Test](tag, "tests") {
  import ModelTypeMapper._

  def testId = column[Int]("test_id", O.PrimaryKey, O.AutoInc)
  def siteId = column[Int]("site_id")
  def modelType = column[ModelTypeEnum]("model_type")
  def startDate = column[Date]("start_date")
  def endDate = column[Date]("end_date")

  override def * =
    (testId, siteId, modelType, startDate, endDate) <> (Test.tupled, Test.unapply)
}

object Tests extends DbConfig {

  val tests = TableQuery[TestTableDef]

  /** Add new test record and return the record with the auto-incremented id */
  def add(test: Test): Future[Test] = {
    val insertQuery = tests returning tests.map(_.testId) into ((test, testId) => test.copy(testId = testId))

    dbConfig.db.run(insertQuery += test)
  }

  def delete(id: Int): Future[Int] = {
    dbConfig.db.run(tests.filter(_.testId === id).delete)
  }

  def get(id: Int): Future[Option[Test]] = {
    dbConfig.db.run(tests.filter(_.testId === id).result.headOption)
  }

  def listAll: Future[Seq[Test]] = {
    dbConfig.db.run(tests.result)
  }

  def listAllForSiteAndDate(siteId: Option[Int], date: Option[Date]): Future[Seq[Test]] = {

    val query1 = siteId.map { sid =>
      tests.filter(_.siteId === sid)
    }.getOrElse(tests)

    val query2 = date.map { dt =>
      query1.filter(test => test.startDate <= dt && test.endDate >= dt) // OK
      //query1.filter(test => dt >= test.startDate && dt <= test.endDate) // NOPE
    }.getOrElse(query1)

    dbConfig.db.run(query2.result)
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment