Skip to content

Instantly share code, notes, and snippets.

@megri
Created June 16, 2016 09:55
Show Gist options
  • Save megri/ae97142a7063376aac134e8c30f0329d to your computer and use it in GitHub Desktop.
Save megri/ae97142a7063376aac134e8c30f0329d to your computer and use it in GitHub Desktop.
Quill: Exception thrown when decoding a null value using mappedEncoder[String, LocalDateTime] for an optional field
import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder}
import java.time.temporal.ChronoField
import java.time.{LocalDateTime, ZoneId}
import io.getquill._
import io.getquill.naming.SnakeCase
import io.getquill.sources.sql.idiom.PostgresDialect
case class Test(date: Option[LocalDateTime])
/**
* Demonstrates a bug in the Option[<custom encoding>] handling when trying to fetch null values
* with a converter that converts String => LocalDateTime
*
* To set up the database, create a table 'test' like so:
*
* CREATE TABLE test("date" TIMESTAMP WITH TIME ZONE)
*
*
* Inserting works for Test(Some(…)) values, whereas Test(None) causes an Exception to be thrown
* Retrieving works as long as there is no null value in the table
*
*
* To insert a null value:
* INSERT INTO (test) VALUES(null)
*
* To insert a working date simply uncomment
*/
object Test {
private val dateTimeTzFormatter =
new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
.optionalStart()
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 6, true)
.optionalEnd()
.appendOffset("+HH:mm", "+00")
.toFormatter()
// while the encoder needs to encode to java.util.Date, the decoder can handle String => LocalDateTime
// that is, unless there is a null value in the database, in which case an Exception occurs
private implicit val decodeLocalDateTime =
mappedEncoding[String, LocalDateTime](text =>
LocalDateTime.from(dateTimeTzFormatter.parse(text))
)
// needs to be LocalDateTime => java.util.Date to function due to Postgres refusing to implicitly
// convert String => TIMESTAMP WITH TIME ZONE
private implicit val encodeLocalDateTime =
mappedEncoding[LocalDateTime, java.util.Date] { ldt =>
val zoneOffset = ZoneId.systemDefault().getRules.getOffset(ldt)
java.util.Date.from(ldt.toInstant(zoneOffset))
}
def main(args: Array[String]): Unit = {
val db = source(new JdbcSourceConfig[PostgresDialect, SnakeCase]("db"))
def exposeJdbcNextException[A](op: => A): A = {
try {
op
} catch {
case e: java.sql.BatchUpdateException =>
throw e.getNextException
}
}
exposeJdbcNextException {
db.transaction {
// insert valid row
// db.run {
// quote {
// query[Test].insert
// }
// }(List(Test(Some(LocalDateTime.parse("2016-12-12T00:00"))))
// insert null
// NOTE: this fails to insert a null for some reason; uncomment to see error
// db.run {
// quote {
// query[Test].insert
// }
// }(List(Test(None)))
// retrieve: will throw exception if a null is present in the table
db.run {
quote {
query[Test]
}
}.foreach(println)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment