Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Postgres Java time instances #1735

Merged
merged 1 commit into from
Aug 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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