Skip to content

Commit

Permalink
Merge pull request #1789 from guymers/java-time-meta
Browse files Browse the repository at this point in the history
Improve Java time meta experience
  • Loading branch information
jatcwang authored Jan 11, 2024
2 parents 2eea576 + 5739211 commit 370c680
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 262 deletions.
5 changes: 1 addition & 4 deletions modules/core/src/main/scala/doobie/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ package object doobie
object implicits
extends free.Instances
with generic.AutoDerivation
with syntax.AllSyntax
with util.meta.SqlMeta
with util.meta.TimeMeta
with util.meta.LegacyMeta {
with syntax.AllSyntax {

// re-export these instances so `Meta` takes priority, must be in the object
implicit def metaProjectionGet[A](implicit m: Meta[A]): Get[A] = Get.metaProjection
Expand Down
37 changes: 0 additions & 37 deletions modules/core/src/main/scala/doobie/util/meta/legacymeta.scala

This file was deleted.

19 changes: 13 additions & 6 deletions modules/core/src/main/scala/doobie/util/meta/meta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ final class Meta[A](val get: Get[A], val put: Put[A]) {
/** Module of constructors and instances for `Meta`. */
object Meta extends MetaConstructors
with MetaInstances
with SqlMetaInstances
with TimeMetaInstances
{

/** Summon the `Meta` instance if possible. */
Expand Down Expand Up @@ -85,6 +87,17 @@ trait MetaConstructors {
Put.Basic.one(jdbcType, put, update)
)

def oneObject[A: TypeName](
jdbcType: JdbcType,
jdbcSourceSecondary: List[JdbcType],
clazz: Class[A]
): Meta[A] = one(
jdbcType = jdbcType,
jdbcSourceSecondary = jdbcSourceSecondary,
_.getObject(_, clazz),
_.setObject(_, _),
_.updateObject(_, _)
)
}

/**
Expand Down Expand Up @@ -239,10 +252,4 @@ trait MetaInstances { this: MetaConstructors =>
implicit val ScalaBigDecimalMeta: Meta[BigDecimal] =
BigDecimalMeta.imap(BigDecimal.apply)(_.bigDecimal)

import doobie.implicits.javasql.DateMeta

/** @group Instances */
implicit val JavaUtilDateMeta: Meta[java.util.Date] =
DateMeta.imap[java.util.Date](a => a)(d => new java.sql.Date(d.getTime))

}
10 changes: 4 additions & 6 deletions modules/core/src/main/scala/doobie/util/meta/sqlmeta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ package doobie.util.meta

import doobie.enumerated.JdbcType._

trait SqlMeta {

object javasql extends MetaConstructors with SqlMetaInstances

}

trait SqlMetaInstances { this: MetaConstructors =>

/** @group Instances */
Expand All @@ -35,4 +29,8 @@ trait SqlMetaInstances { this: MetaConstructors =>
List(Char, VarChar, LongVarChar, Date, Time),
_.getTimestamp(_), _.setTimestamp(_, _), _.updateTimestamp(_, _))

/** @group Instances */
implicit val JavaUtilDateMeta: Meta[java.util.Date] =
DateMeta.imap[java.util.Date](a => a)(d => new java.sql.Date(d.getTime))

}
77 changes: 25 additions & 52 deletions modules/core/src/main/scala/doobie/util/meta/timemeta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,48 @@
package doobie.util.meta

import doobie.enumerated.JdbcType._
import Predef._

trait TimeMeta {

@deprecated("Use doobie.implicits.javatimedrivernative instead. If you are using a database which doobie directly integrates with, " +
"You won't need this import anymore as datetime instances are provided in the DB-specific implicit import. " +
"e.g. for PostgreSQL: `import doobie.postgres.implicits._`. ",
since = "0.11.0"
)
object javatime extends MetaConstructors with TimeMetaInstances

/**
* Use this import if you want to use the driver-native support for java.time.* types.
* This means that the java.time value is passed straight to the JDBC driver you're using
* without being converted to java.sql.* types (Unlike doobie.implicits.legacy.instant/localdate)
*/
object javatimedrivernative extends MetaConstructors with TimeMetaInstances

}

/**
* Instances for Java time classes that follow the JDBC specification.
*/
trait TimeMetaInstances { this: MetaConstructors =>
import Predef.classOf

/** @group Instances */
implicit val JavaTimeInstantMeta: Meta[java.time.Instant] =
Basic.one[java.time.Instant](
Timestamp,
List(Char, VarChar, LongVarChar, Date, Time),
_.getObject(_, classOf[java.time.Instant]), _.setObject(_, _), _.updateObject(_, _))
implicit val JavaOffsetDateTimeMeta: Meta[java.time.OffsetDateTime] =
Basic.oneObject(TimestampWithTimezone, Nil, classOf[java.time.OffsetDateTime])

/** @group Instances */
implicit val JavaTimeLocalDateMeta: Meta[java.time.LocalDate] =
Basic.one[java.time.LocalDate](
Date,
List(Char, VarChar, LongVarChar, Timestamp),
_.getObject(_, classOf[java.time.LocalDate]), _.setObject(_, _), _.updateObject(_, _))
implicit val JavaLocalDateMeta: Meta[java.time.LocalDate] =
Basic.oneObject(Date, Nil, classOf[java.time.LocalDate])

/** @group Instances */
implicit val JavaLocalTimeMeta: Meta[java.time.LocalTime] =
Basic.one[java.time.LocalTime](
Time,
List(Char, VarChar, LongVarChar, Timestamp),
_.getObject(_, classOf[java.time.LocalTime]), _.setObject(_, _), _.updateObject(_, _))
Basic.oneObject(Time, Nil, classOf[java.time.LocalTime])

/** @group Instances */
implicit val JavaTimeLocalDateTimeMeta: Meta[java.time.LocalDateTime] =
Basic.one[java.time.LocalDateTime](
Timestamp,
List(Char, VarChar, LongVarChar, Date, Time),
_.getObject(_, classOf[java.time.LocalDateTime]), _.setObject(_, _), _.updateObject(_, _))
implicit val JavaLocalDateTimeMeta: Meta[java.time.LocalDateTime] =
Basic.oneObject(Timestamp, Nil, classOf[java.time.LocalDateTime])

/** @group Instances */
implicit val JavaOffsetTimeMeta: Meta[java.time.OffsetTime] =
Basic.one[java.time.OffsetTime](
TimeWithTimezone,
List(Char, VarChar, LongVarChar, Timestamp, Time),
_.getObject(_, classOf[java.time.OffsetTime]), _.setObject(_, _), _.updateObject(_, _))
Basic.oneObject(TimeWithTimezone, Nil, classOf[java.time.OffsetTime])

// extra instances not in the spec

/** @group Instances */
implicit val JavaOffsetDateTimeMeta: Meta[java.time.OffsetDateTime] =
Basic.one[java.time.OffsetDateTime](
TimestampWithTimezone,
List(Char, VarChar, LongVarChar, Date, Time, Timestamp),
_.getObject(_, classOf[java.time.OffsetDateTime]), _.setObject(_, _), _.updateObject(_, _))
implicit val JavaInstantMeta: Meta[java.time.Instant] =
JavaOffsetDateTimeMeta.imap(_.toInstant)(_.atOffset(java.time.ZoneOffset.UTC))

/** @group Instances */
implicit val JavaZonedDateTimeMeta: Meta[java.time.ZonedDateTime] =
Basic.one[java.time.ZonedDateTime](
TimestampWithTimezone,
List(Char, VarChar, LongVarChar, Date, Time, Timestamp),
_.getObject(_, classOf[java.time.ZonedDateTime]), _.setObject(_, _), _.updateObject(_, _))
implicit val JavaTimeZoneId: Meta[java.time.ZoneId] = {
def parse(str: String) = try {
Right(java.time.ZoneId.of(str))
} catch {
case e: java.time.DateTimeException => Left(e.getMessage)
}

Meta[String].tiemap(parse(_))(_.getId)
}

}
2 changes: 1 addition & 1 deletion modules/docs/src/main/mdoc/docs/12-Custom-Mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Instances are provided for the following Scala types:
- `Boolean`, `String`, and `Array[Byte]`;
- `Date`, `Time`, and `Timestamp` from the `java.sql` package;
- `Date` from the `java.util` package;
- `Instant`, `LocalDate`, `LocalTime`, `LocalDateTime`, `OffsetTime`, `OffsetDateTime` and `ZonedDateTime` from the `java.time` package; and
- `Instant`, `LocalDate`, `LocalTime`, `LocalDateTime`, `OffsetTime` and `OffsetDateTime` from the `java.time` package; and
- single-element case classes wrapping one of the above types.

The `java.time` instances may require a separate import , dependent on your Database Driver . See the [doobie-faq](https://tpolecat.github.io/doobie/docs/17-FAQ.html#how-do-i-use-java-time-types-with-doobie-) for details
Expand Down
8 changes: 1 addition & 7 deletions modules/docs/src/main/mdoc/docs/15-Extensions-PostgreSQL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,11 @@ import doobie.postgres.implicits._

### Java 8 Time Types (JSR310)

An explicit import is required to bring in mappings for `java.time.OffsetDateTime` / `java.time.Instant` / `java.time.LocalDateTime` / `java.time.LocalDate` / `java.time.LocalTime`

```scala mdoc:silent
import doobie.postgres.implicits._
```

To ensure **doobie** performs the conversion correctly between Java 8 time types and PostgreSQL Date/Time types when handling timezones or the lack thereof.
The correct combination of date/time types should be used:

- `TIMESTAMP` maps to `java.time.LocalDateTime`
- `TIMESTAMPTZ` maps to `java.time.Instant`, or `java.time.OffsetDateTime`
- `TIMESTAMPTZ` maps to `java.time.Instant` or `java.time.OffsetDateTime`
- `DATE` maps to `java.time.LocalDate`
- `TIME` maps to `java.time.LocalTime`

Expand Down
19 changes: 0 additions & 19 deletions modules/docs/src/main/mdoc/docs/17-FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,22 +202,3 @@ implicit val nesMeta: Meta[NonEmptyString] = {
distinct.string("nes").imap(NonEmptyString.apply)(_.value)
}
```

## How do I use `java.time` types with Doobie?

This depends on whether the underlying JDBC driver you're using supports `java.time.*` types natively.
("native support" means that you can hand the driver e.g. a value of java.time.Instant and it will know how to convert
that to a value on-the-wire that the actual database can understand)

If you're using PostgreSQL, you can import that instances via `import doobie.postgres.implicits._`

If your JDBC driver supports the java.time types you're using natively, use `import doobie.implicits.javatimedrivernative._`.

| Database driver | java.time.Instant | java.time.LocalDate |
| --- | --- | --- |
| Postgres (org.postgresql.Driver) | `doobie.postgres.implicits._` | `doobie.postgres.implicits._` |
| MySQL (com.mysql.jdbc.Driver) | `doobie.implicits.legacy.instant._` | `doobie.implicits.legacy.localdate._` |

References:

- [Postgres JDBC - Using Java 8 Date and Time classes](https://jdbc.postgresql.org/documentation/query/#using-java-8-date-and-time-classes)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package example
import doobie._, doobie.implicits._

import java.sql.Date
import doobie.implicits.javasql._

object CustomReadWrite {

Expand Down
Loading

0 comments on commit 370c680

Please sign in to comment.