Skip to content

FIX - Issue 325 - Column[Instant] loose nano seconds precision#326

Merged
cchantep merged 1 commit into
playframework:masterfrom
FXHibon:Fix_java.sql.timestamp_nano_precision
Jan 6, 2021
Merged

FIX - Issue 325 - Column[Instant] loose nano seconds precision#326
cchantep merged 1 commit into
playframework:masterfrom
FXHibon:Fix_java.sql.timestamp_nano_precision

Conversation

@FXHibon
Copy link
Copy Markdown
Contributor

@FXHibon FXHibon commented Jan 6, 2021

Pull Request Checklist

Fixes

Fixes #325

Purpose

Fix the default instance of Column[Instant] in the JavaTimeColumn trait, so we don't loose the nano second precision during the SQL result reads.

Background Context

When reading an SQL result, TIMESTAMP[TZ] from database is mapped on the java.sql.Timestamp type. Therefore, the Column[Instant] should be able to transform a given java.sql.Timestamp into an java.time.Instant

As java.sql.Timestamp inherits java.util.Date, the first branch of following pattern-matching will be selected

   value match {
      case date: java.util.Date => Right(Instant ofEpochMilli date.getTime)
      case time: Long => Right(Instant ofEpochMilli time)
      case TimestampWrapper1(ts) => Ts(ts)(Instant ofEpochMilli _.getTime)
      case TimestampWrapper2(ts) => Ts(ts)(Instant ofEpochMilli _.getTime)
      case _ => Left(TypeDoesNotMatch(s"Cannot convert $value: ${className(value)} to Java8 Instant for column $qualified"))
    }

https://github.com/playframework/anorm/blob/master/core/src/main/scala/anorm/Column.scala#L715

The problem is that java.util.Date is precised at the miliseconds level, and not the nano seconds level. Dealing with a java.sql.Timestamp as as java.util.Date will loose this precision, as we can understand immediatly with the following code:

Instant ofEpochMilli date.getTime

(getTime returns EPOCH miliseconds, as explained in javadoc)

I wonder why the pattern-matching wasn't using directly the java.sql.Timestamp type? Maybe is there an hidden reason I couldn't understand?

Adding a line (in first position) with a pattern on java.sql.Timestamp seems to be ok, especially since Timestamp can be directly converted to Instant with the method .toInstant.

The 2 lines that matches TimestampWrapper1 and TimestampWrapper2 would loose in the same way, because it build the Instant using only the miliseconds.

I took this opportunty to use ts.toInstant here to, to avoid silent data loss behavior.

Actually, I'm not sure to understand when these TimestampWrapper become useful, but I might not have the full picture I my mind.

References

The issue is explained in details here #325

@FXHibon FXHibon changed the title update scala / add .bsp in ignored files / fix java.sql.Timestamp nan… FIX - Issue 325 - Column[Instant] loose nano seconds precision Jan 6, 2021
Comment thread core/src/main/scala/anorm/Column.scala Outdated
Comment thread core/src/test/scala/anorm/JavaTimeSpec.scala Outdated
Comment thread core/src/main/scala/anorm/Column.scala
withQueryResult(
timestampList :+ Timestamp.from(instantWithNanoPrecision)) { implicit con =>
SQL("SELECT ts").as(scalar[Instant].single).
aka("parsed instant") must_=== instantWithNanoPrecision
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For history, this test without code change:

[error]   x be parsed from timestamp with nano precision
[error]    parsed instant '2021-01-06T08:45:26.441Z != 2021-01-06T08:45:26.441477Z' (JavaTimeSpec.scala:46)
[error] Actual:   ...6.441[]Z
[error] Expected: ...6.441[477]Z

Comment thread build.sbt
Copy link
Copy Markdown
Member

@cchantep cchantep left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution

@cchantep cchantep merged commit aad8a30 into playframework:master Jan 6, 2021
@FXHibon FXHibon deleted the Fix_java.sql.timestamp_nano_precision branch January 7, 2021 07:26
@FXHibon
Copy link
Copy Markdown
Contributor Author

FXHibon commented Jan 22, 2021

hello @cchantep
Would you be able to release a version 2.6.9 of anorm that would include this PR?

@cchantep
Copy link
Copy Markdown
Member

Rather 2.6.10

@cchantep
Copy link
Copy Markdown
Member

https://search.maven.org/artifact/org.playframework.anorm/anorm_2.12/2.6.9/jar

gaeljw pushed a commit that referenced this pull request May 24, 2026
…714)

* 🐛 fix: preserve nanosecond precision in Column[LocalDateTime] (#525)

  `columnToLocalDateTime` previously delegated through `temporalValueTo`,
  which extracts time as `Timestamp#getTime` (millisecond epoch) and
  reconstructs via `Instant.ofEpochMilli`, truncating sub-millisecond
  precision.  Route through `instantValueTo` instead so `Timestamp#toInstant`
  preserves nanoseconds, mirroring the pattern of #326 (Instant) and #409
  (ZonedDateTime).

  Regression test added to JavaTimeColumnSpec.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

* ♻️ refactor: address #714 review (inline helper, drop comments)

  Per @gaeljw's review:
  - Inline the instantToLocalDateTime helper as an `Instant` lambda
    passed directly to instantValueTo, matching columnToZonedDateTime.
  - Drop the routing rationale comments; the nano-precision test covers
    the behaviour.

  The `case localDateTime: LocalDateTime => Right(localDateTime)`
  short-circuit stays to avoid a ZoneId.systemDefault round-trip on
  non-UTC JVMs.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

---------

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default Column[Instant] instance loose nano seconds precision

2 participants