Skip to content

feat(jdbc): add timezone-aware temporal types - #243

Closed
magnusp wants to merge 3 commits into
task/238-pr-3-wrappersfrom
task/238-pr-4-offset-types
Closed

magnusp wants to merge 3 commits into
task/238-pr-3-wrappersfrom
task/238-pr-4-offset-types

Conversation

@magnusp

@magnusp magnusp commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Why

JDBC 4.3 Appendix B names TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE in Tables B-4, B-5, and B-6, but the driver had no declared-type entry for either. A declared offset column fell back to storage-class handling and getObject returned text instead of the Java offset types.

What changed

  • Map TIME_WITH_TIMEZONE to OffsetTime and TIMESTAMP_WITH_TIMEZONE to OffsetDateTime in the declared-type registry.
  • Parse ISO-8601 offset text in getObject(int) and getObject(int, Class); accept both Z and numeric offsets.
  • Serialize OffsetTime and OffsetDateTime as ISO-8601 text in the no-target and target paths. JdbcValueConverter already had this from PR-2; it was verified, not rewritten.
  • Reject malformed offset text with SQLException.
  • Reject non-getObject getters on the two new declared types, which is what Table B-6 lists for them.
  • Add unit tests for Z, +01:00, and malformed offset text, plus declared-column reads and metadata.
  • Add the missing B-6 getObject cells to the server suite.

Scope

Only the PR-4 files:

  • libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/JdbcTypeMapping.java
  • libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSet.java
  • libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/Jdbc4AppendixBReadTest.java
  • libsql-itests/src/test/java/com/github/magnusp/libsql/itest/Jdbc4AppendixBTypeMappingIT.java

Local date and time paths are unchanged. The Appendix B oracle was not weakened.

Tradeoffs

  • OffsetDateTime accepts both the ISO-8601 T separator and the SQLite space separator. Strict ISO-8601 parsing alone would reject the text form the existing B-6 fixture uses.
  • The new declared types expose only getObject, per Table B-6. The rest of the unlisted-cell matrix remains PR-5's strict-conversion work; this PR applies that rule only to the two new types so every offset cell is green.
  • getObject(int, Class) parses offset text regardless of declared type, matching the existing temporal getter behavior.

Blast Radius

  • Declared TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE columns now resolve to offset classes in getObject, getColumnType, and getColumnClassName.
  • Columns of other declared types keep their previous conversion paths.
  • Expression columns keep the storage fallback and are unaffected.
  • No SQL, connection, transaction, or metadata interaction changes.

Verification

  1. ./mvnw -q -pl libsql-jdbc -Dtest='Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes' -Dsurefire.failIfNoSpecifiedTests=false test
    • 972 B-6 cells, 579 failures: 2 listed-cell failures (ARRAY, REF) and 577 unlisted-cell failures, both deferred to PR-5.
    • Offset cells: 54 total (2 listed, 52 unlisted), 0 failures.
  2. ./mvnw -q -pl libsql-jdbc test
    • 1505 tests, 586 failures, 0 errors, 0 skipped.
    • B-1 3, B-3 3, B-5 0, B-6 580.
    • Every failure is in Jdbc4AppendixBReadTest; no non-Appendix-B failure.
  3. ./mvnw -q spotless:check
    • Exit 0.
  4. Installed libsql-hrana-codec,libsql-client,libsql-jdbc, then ran Jdbc4AppendixBTypeMappingIT against sqld.
    • 148 tests, 22 failures, 0 errors.
    • 0 offset-related failures. The 22 are the known ARRAY/STRUCT/REF/JAVA_OBJECT and pre-existing B-2/B-5 gaps.

Ready for review; not merged and not retargeted.

Map TIME_WITH_TIMEZONE to OffsetTime and TIMESTAMP_WITH_TIMEZONE to OffsetDateTime, parse ISO-8601 offset text, and reject unlisted getters on the new declared types.
Assert Z and +01:00 parse to OffsetTime and OffsetDateTime, malformed offset text raises SQLException, and declared offset columns return the offset classes.
Add the missing B-6 getObject cells for TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE to the server suite.
@magnusp

magnusp commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

Verdict: CLEAN at 3d11c0c. Base 4067d42, branch task/238-pr-4-offset-types, parent PR #242.

Ten lanes PASS at the verdict head. Transcripts are under /tmp/swarm-pr-4/.

Lane Check Result
1 Regression against trunk trunk returns a String from getObject on a declared offset column; head returns OffsetTime
2 OffsetTime with no target 03:04:05Z round trips as OffsetTime
3 OffsetDateTime with no target 2024-01-02T03:04:05Z round trips as OffsetDateTime
4 OffsetTime with target TIME_WITH_TIMEZONE stored 03:04:05+01:00
5 OffsetDateTime with target TIMESTAMP_WITH_TIMEZONE stored 2024-01-02T03:04:05+01:00
6 OffsetDateTime with target TIME_WITH_TIMEZONE stored 03:04:05+01:00
7 Declared TIME_WITH_TIMEZONE getObject returns OffsetTime with +01:00
8 Declared TIMESTAMP_WITH_TIMEZONE getObject returns OffsetDateTime
9 Malformed offset text SQLException: Bad value for type OffsetDateTime: bogus
10 Zone forms Z and +01:00 both parse and keep their offsets

Units are 1,505 tests, 586 failures, 0 errors, all in the Appendix B read suite. B-1 stays at 3, B-3 at 3, B-5 at 0, and every offset cell passes. Spotless is clean. The integration suite is 148 tests, 22 failures, with no offset mention; the remainder is the PR-5 unsupported-type work.

Perf is PASS. Over 100,000 calls, offset parsing is 2.6 microseconds at p99 and offset serialization is 0.7 microseconds, both far below the 20 microsecond budget. The timestamp control at the head is 2.6 microseconds against 3.1 at trunk, so it did not regress.

The PR also rejects non-getObject getters on declared offset columns, which is what the B-6 oracle rows require. That behavior is wider than the plan's summary and is recorded here for the reviewer.

CI is red only where Appendix B is red, by design. No review-bot comments to triage.

STACK-READY. PR-5 branches from this head.

@magnusp

magnusp commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

Verdict correction, lane evidence. Lanes 2 through 6 were first captured with an intermediate probe. One transcript showed Bad value for type OffsetTime: text, caused by the probe reading the typeof column instead of the value column, not by the driver. Lanes 2 through 6 were rerun at 3d11c0c after installing that head. The clean transcripts are under /tmp/swarm-pr-4-round2/, with the installed head SHA in each file. The verdict is unchanged.

@magnusp
magnusp added this pull request to stack #246 October 25, 2026 09:09
magnusp added a commit that referenced this pull request Oct 25, 2026
## Why

The driver mapped result values by storage class, so the declared type
did not control metadata or `getObject`, and `setObject` ignored the
target type. JDBC 4.3 Appendix B is the oracle. Seven stacked pull
requests implement it. The stack cannot land link by link because the
`main` ruleset requires the full build green at every head, and the
Appendix B oracle is red until the last link. This PR lands the verified
tip as one commit.

## What changed

- Add the Appendix B oracle suite and the vendor table, then resolve one
JDBC type per declared column for metadata and `getObject`.
- Honor the `setObject` target type, add the missing getters and LOB
wrappers, and support the offset temporal types.
- Mark `ARRAY`, `STRUCT`, `REF`, `JAVA_OBJECT`, and `DISTINCT` N/A and
reject unlisted Table B-6 conversions.
- Publish the compliance profile, quarantine the four xerial tests that
conflict with the spec, and record the policy.

## Scope

The seven stacked PRs are #239, #240, #241, #242, #243, #244, and #245.
No unrelated changes. `vendor/JDBC4-TYPE-MAPPING.md` is the oracle.

## Tradeoffs

- The five unrepresentable types throw instead of emulating them.
- The conformance quarantine is method-level. The lost checks are named
in `libsql-conformance/README.md`.
- An expression column with no `decltype` keeps the storage fallback,
documented as a profile exception.

## Blast Radius

Metadata and `getObject` now follow the declared type, so a `BIGINT`
holding 42 returns `Long`, and an unlisted getter conversion throws. The
default build was red from #239 through #245 by design and is green at
this tip.

## Verification

- `clean verify` at `0861960`: 2,006 tests, 0 failures, 7 skipped, about
63 seconds.
- Appendix B unit 1,050 of 1,050, integration 148 of 148, conformance
270 of 270 with 7 skipped.
- Clean clone at the same SHA: unit 1,510 of 1,510, integration 148 of
148.
- Each link has a verdict and ten live lanes. The verdict comments are
on #239 through #245.
@magnusp

magnusp commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #247. The verified stack tip at 0861960 landed as one commit on main (e41cac1) because the main ruleset requires green required checks at every head, and the Appendix B oracle is red until the last link. The verdict and review record on this PR are unchanged.

@magnusp magnusp closed this Oct 25, 2026
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.

1 participant