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

Improve Java time meta experience #1789

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 0 additions & 3 deletions modules/core/src/main/scala/doobie/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,5 @@ package object doobie
object implicits
extends free.Instances
with syntax.AllSyntax
with util.meta.SqlMeta
with util.meta.TimeMeta
with util.meta.LegacyMeta

}
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))

}
89 changes: 41 additions & 48 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,68 @@
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,
List(TimeWithTimezone),
classOf[java.time.OffsetDateTime]
)

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

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

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

/** @group Instances */
implicit val JavaOffsetTimeMeta: Meta[java.time.OffsetTime] =
Basic.one[java.time.OffsetTime](
Basic.oneObject(
TimeWithTimezone,
List(Char, VarChar, LongVarChar, Timestamp, Time),
_.getObject(_, classOf[java.time.OffsetTime]), _.setObject(_, _), _.updateObject(_, _))
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 @@ -76,17 +76,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.ZonedDateTime` / `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`, `java.time.ZonedDateTime` 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 @@ -201,22 +201,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/head/8-date-time.html)
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