Skip to content

Commit

Permalink
Merge pull request #1735 from guymers/postgres-java-time
Browse files Browse the repository at this point in the history
Update Postgres Java time instances
  • Loading branch information
jatcwang authored Aug 28, 2022
2 parents 750177f + 508de12 commit 63c786d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 51 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ lazy val h2Version = "1.4.200"
lazy val hikariVersion = "4.0.3" // N.B. Hikari v4 introduces a breaking change via slf4j v2
lazy val kindProjectorVersion = "0.11.2"
lazy val postGisVersion = "2.5.1"
lazy val postgresVersion = "42.3.5"
lazy val postgresVersion = "42.4.2"
lazy val refinedVersion = "0.9.28"
lazy val scalaCheckVersion = "1.15.4"
lazy val scalatestVersion = "3.2.10"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ trait JavaTimeInstances extends MetaConstructors {
implicit val JavaTimeOffsetDateTimeMeta: Meta[java.time.OffsetDateTime] =
Basic.one[java.time.OffsetDateTime](
JT.Timestamp,
List(JT.Char, JT.VarChar, JT.LongVarChar, JT.Date, JT.Time),
List(JT.Time),
_.getObject(_, classOf[java.time.OffsetDateTime]), _.setObject(_, _), _.updateObject(_, _))

/**
Expand All @@ -51,7 +51,7 @@ trait JavaTimeInstances extends MetaConstructors {
implicit val JavaTimeLocalDateTimeMeta: Meta[java.time.LocalDateTime] =
Basic.one[java.time.LocalDateTime](
JT.Timestamp,
List(JT.Char, JT.VarChar, JT.LongVarChar, JT.Date, JT.Time),
Nil,
_.getObject(_, classOf[java.time.LocalDateTime]), _.setObject(_, _), _.updateObject(_, _))

/**
Expand All @@ -60,7 +60,7 @@ trait JavaTimeInstances extends MetaConstructors {
implicit val JavaTimeLocalDateMeta: Meta[java.time.LocalDate] =
Basic.one[java.time.LocalDate](
JT.Date,
List(JT.Char, JT.VarChar, JT.LongVarChar, JT.Timestamp),
List(JT.Timestamp),
_.getObject(_, classOf[java.time.LocalDate]), _.setObject(_, _), _.updateObject(_, _))

/**
Expand All @@ -69,7 +69,7 @@ trait JavaTimeInstances extends MetaConstructors {
implicit val JavaTimeLocalTimeMeta: Meta[java.time.LocalTime] =
Basic.one[java.time.LocalTime](
JT.Time,
List(JT.Char, JT.VarChar, JT.LongVarChar, JT.Timestamp),
Nil,
_.getObject(_, classOf[java.time.LocalTime]), _.setObject(_, _), _.updateObject(_, _))

}
111 changes: 66 additions & 45 deletions modules/postgres/src/test/scala/doobie/postgres/CheckSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
package doobie.postgres

import cats.effect.IO
import doobie._, doobie.implicits._
import doobie._
import doobie.implicits._
import doobie.postgres.enums._
import doobie.postgres.implicits._
import doobie.util.analysis.{ColumnTypeWarning, ColumnTypeError}

import java.time.{Instant, OffsetDateTime, LocalDate, LocalDateTime, LocalTime}
import doobie.util.analysis.{ColumnTypeError, ColumnTypeWarning, ParameterTypeError}
import java.time.{Instant, LocalDate, LocalDateTime, LocalTime, OffsetDateTime}

class CheckSuite extends munit.FunSuite {

Expand All @@ -35,18 +35,18 @@ class CheckSuite extends munit.FunSuite {
successReadUnfortunately[OffsetDateTime](sql"SELECT '2019-02-13T22:03:21.000' :: TIMESTAMP")
successWriteUnfortunately[OffsetDateTime](t, "TIMESTAMP")

warnRead[OffsetDateTime](sql"SELECT '2019-02-13T22:03:21.000' :: TEXT")
warnRead[OffsetDateTime](sql"SELECT '03:21' :: TIME")
failedRead[OffsetDateTime](sql"SELECT '2019-02-13T22:03:21.000' :: TEXT")
_warnRead[OffsetDateTime](sql"SELECT '03:21' :: TIME") // driver cannot read but TIME and TIMETZ are returned as the same JDBC type
warnRead[OffsetDateTime](sql"SELECT '03:21' :: TIMETZ")
warnRead[OffsetDateTime](sql"SELECT '2019-02-13' :: DATE")
failedRead[OffsetDateTime](sql"SELECT '2019-02-13' :: DATE")

successWriteUnfortunately[OffsetDateTime](t, "TEXT")
successWriteUnfortunately[OffsetDateTime](t, "TIME")
successWriteUnfortunately[OffsetDateTime](t, "TIMETZ")
successWriteUnfortunately[OffsetDateTime](t, "DATE")
errorWrite[OffsetDateTime](t, "TEXT")
errorWrite[OffsetDateTime](t, "TIME")
errorWrite[OffsetDateTime](t, "TIMETZ")
errorWrite[OffsetDateTime](t, "DATE")

failedRead[OffsetDateTime](sql"SELECT '123' :: BYTEA")
successWriteUnfortunately[OffsetDateTime](t, "BYTEA")
errorWrite[OffsetDateTime](t, "BYTEA")
}

test("Instant Read and Write typechecks") {
Expand All @@ -57,18 +57,18 @@ class CheckSuite extends munit.FunSuite {
successReadUnfortunately[Instant](sql"SELECT '2019-02-13T22:03:21.000' :: TIMESTAMP")
successWriteUnfortunately[Instant](t, "TIMESTAMP")

warnRead[Instant](sql"SELECT '2019-02-13T22:03:21.000' :: TEXT")
warnRead[Instant](sql"SELECT '03:21' :: TIME")
failedRead[Instant](sql"SELECT '2019-02-13T22:03:21.000' :: TEXT")
_warnRead[Instant](sql"SELECT '03:21' :: TIME") // driver cannot read but TIME and TIMETZ are returned as the same JDBC type
warnRead[Instant](sql"SELECT '03:21' :: TIMETZ")
warnRead[Instant](sql"SELECT '2019-02-13' :: DATE")
failedRead[Instant](sql"SELECT '2019-02-13' :: DATE")

successWriteUnfortunately[Instant](t, "TEXT")
successWriteUnfortunately[Instant](t, "TIME")
successWriteUnfortunately[Instant](t, "TIMETZ")
successWriteUnfortunately[Instant](t, "DATE")
errorWrite[Instant](t, "TEXT")
errorWrite[Instant](t, "TIME")
errorWrite[Instant](t, "TIMETZ")
errorWrite[Instant](t, "DATE")

failedRead[Instant](sql"SELECT '123' :: BYTEA")
successWriteUnfortunately[Instant](t, "BYTEA")
errorWrite[Instant](t, "BYTEA")
}

test("LocalDateTime Read and Write typechecks") {
Expand All @@ -79,18 +79,18 @@ class CheckSuite extends munit.FunSuite {
successReadUnfortunately[LocalDateTime](sql"SELECT '2019-02-13T22:03:21.051' :: TIMESTAMPTZ")
successWriteUnfortunately[LocalDateTime](t, "TIMESTAMPTZ")

warnRead[LocalDateTime](sql"SELECT '2019-02-13T22:03:21.051' :: TEXT")
warnRead[LocalDateTime](sql"SELECT '03:21' :: TIME")
warnRead[LocalDateTime](sql"SELECT '03:21' :: TIMETZ")
warnRead[LocalDateTime](sql"SELECT '2019-02-13' :: DATE")
failedRead[LocalDateTime](sql"SELECT '2019-02-13T22:03:21.051' :: TEXT")
failedRead[LocalDateTime](sql"SELECT '03:21' :: TIME")
failedRead[LocalDateTime](sql"SELECT '03:21' :: TIMETZ")
failedRead[LocalDateTime](sql"SELECT '2019-02-13' :: DATE")

successWriteUnfortunately[LocalDateTime](t, "TEXT")
successWriteUnfortunately[LocalDateTime](t, "TIME")
successWriteUnfortunately[LocalDateTime](t, "TIMETZ")
successWriteUnfortunately[LocalDateTime](t, "DATE")
errorWrite[LocalDateTime](t, "TEXT")
errorWrite[LocalDateTime](t, "TIME")
errorWrite[LocalDateTime](t, "TIMETZ")
errorWrite[LocalDateTime](t, "DATE")

failedRead[LocalDateTime](sql"SELECT '123' :: BYTEA")
successWriteUnfortunately[LocalDateTime](t, "BYTEA")
errorWrite[LocalDateTime](t, "BYTEA")
}

test("LocalDate Read and Write typechecks") {
Expand All @@ -99,51 +99,59 @@ class CheckSuite extends munit.FunSuite {
successWrite[LocalDate](t, "DATE")

warnRead[LocalDate](sql"SELECT '2015-02-23T01:23:13.000' :: TIMESTAMP")
warnRead[LocalDate](sql"SELECT '2015-02-23T01:23:13.000Z' :: TIMESTAMPTZ")
warnRead[LocalDate](sql"SELECT '2015-02-23' :: TEXT")
_warnRead[LocalDate](sql"SELECT '2015-02-23T01:23:13.000Z' :: TIMESTAMPTZ") // driver cannot read but TIMESTAMP and TIMESTAMPTZ are returned as the same JDBC type
failedRead[LocalDate](sql"SELECT '2015-02-23' :: TEXT")
failedRead[LocalDate](sql"SELECT '03:21' :: TIME")
failedRead[LocalDate](sql"SELECT '03:21' :: TIMETZ")

successWriteUnfortunately[LocalDate](t, "TEXT")
successWriteUnfortunately[LocalDate](t, "TIME")
successWriteUnfortunately[LocalDate](t, "TIMETZ")
successWriteUnfortunately[LocalDate](t, "DATE")
errorWrite[LocalDate](t, "TEXT")
errorWrite[LocalDate](t, "TIME")
errorWrite[LocalDate](t, "TIMETZ")

failedRead[LocalDate](sql"SELECT '123' :: BYTEA")
successWriteUnfortunately[LocalDate](t, "BYTEA")
errorWrite[LocalDate](t, "BYTEA")
}

test("LocalTime Read and Write typechecks") {
val t = LocalTime.parse("23:13")
successRead[LocalTime](sql"SELECT '23:13' :: TIME")
successWrite[LocalTime](t, "TIME")

warnRead[LocalTime](sql"SELECT '2015-02-23T01:23:13.000' :: TIMESTAMP")
warnRead[LocalTime](sql"SELECT '2015-02-23T01:23:13.000Z' :: TIMESTAMPTZ")
warnRead[LocalTime](sql"SELECT '2015-02-23' :: TEXT")
failedRead[LocalTime](sql"SELECT '2015-02-23T01:23:13.000' :: TIMESTAMP")
failedRead[LocalTime](sql"SELECT '2015-02-23T01:23:13.000Z' :: TIMESTAMPTZ")
failedRead[LocalTime](sql"SELECT '2015-02-23' :: TEXT")
failedRead[LocalTime](sql"SELECT '2015-02-23' :: DATE")

successWriteUnfortunately[LocalTime](t, "TEXT")
successWriteUnfortunately[LocalTime](t, "TIME")
errorWrite[LocalTime](t, "TEXT")
successWriteUnfortunately[LocalTime](t, "TIMETZ")
successWriteUnfortunately[LocalTime](t, "DATE")
errorWrite[LocalTime](t, "DATE")

failedRead[LocalTime](sql"SELECT '123' :: BYTEA")
successWriteUnfortunately[LocalTime](t, "BYTEA")
errorWrite[LocalTime](t, "BYTEA")
}

private def successRead[A: Read](frag: Fragment): Unit = {
val analysisResult = frag.query[A].analysis.transact(xa).unsafeRunSync()
assertEquals(analysisResult.columnAlignmentErrors, Nil)

val result = frag.query[A].unique.transact(xa).attempt.unsafeRunSync()
assert(result.isRight)
}

private def successWrite[A: Put](value: A, dbType: String): Unit = {
val frag = sql"SELECT $value :: " ++ Fragment.const(dbType)
val analysisResult = frag.update.analysis.transact(xa).unsafeRunSync()
assertEquals(analysisResult.columnAlignmentErrors, Nil)
assertEquals(analysisResult.parameterAlignmentErrors, Nil)
}

private def warnRead[A: Read](frag: Fragment): Unit = {
_warnRead[A](frag)

val result = frag.query[A].unique.transact(xa).attempt.unsafeRunSync()
assert(result.isRight)
}

private def _warnRead[A: Read](frag: Fragment): Unit = {
val analysisResult = frag.query[A].analysis.transact(xa).unsafeRunSync()
val errorClasses = analysisResult.columnAlignmentErrors.map(_.getClass)
assertEquals(errorClasses, List(classOf[ColumnTypeWarning]))
Expand All @@ -153,11 +161,24 @@ class CheckSuite extends munit.FunSuite {
val analysisResult = frag.query[A].analysis.transact(xa).unsafeRunSync()
val errorClasses = analysisResult.columnAlignmentErrors.map(_.getClass)
assertEquals(errorClasses, List(classOf[ColumnTypeError]))

val result = frag.query[A].unique.transact(xa).attempt.unsafeRunSync()
assert(result.isLeft)
}

private def errorWrite[A: Put](value: A, dbType: String): Unit = {
val frag = sql"SELECT $value :: " ++ Fragment.const(dbType)
val analysisResult = frag.update.analysis.transact(xa).unsafeRunSync()
val errorClasses = analysisResult.parameterAlignmentErrors.map(_.getClass)
assertEquals(errorClasses, List(classOf[ParameterTypeError]))
}

private def successWriteUnfortunately[A: Put](value: A, dbType: String): Unit = successWrite(value, dbType)

// Some DB types really shouldn't type check but driver is too lenient
private def successReadUnfortunately[A: Read](frag: Fragment): Unit = successRead(frag)
private def successReadUnfortunately[A: Read](frag: Fragment): Unit = {
val analysisResult = frag.query[A].analysis.transact(xa).unsafeRunSync()
assertEquals(analysisResult.columnAlignmentErrors, Nil)
}

}
2 changes: 1 addition & 1 deletion project/build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Required for the freegen definition for postgres in ../build.sbt
val postgresVersion = "42.3.5"
val postgresVersion = "42.4.2"
libraryDependencies += "org.postgresql" % "postgresql" % postgresVersion

0 comments on commit 63c786d

Please sign in to comment.