Created
June 16, 2016 09:55
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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