Skip to content

fix(jdbc): gate getObject numeric targets on storage class - #69

Merged
magnusp merged 1 commit into
mainfrom
fix/34-getobject-storage-type
Oct 21, 2026
Merged

magnusp merged 1 commit into
mainfrom
fix/34-getobject-storage-type

Conversation

@magnusp

@magnusp magnusp commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Closes #34. Part of #54.

What changed

  • LibsqlResultSet.getObject(int, Class) now gates Integer, Long, Float, and Double on the current value's SQLite storage class, matching xerial 3.53.4.0 JDBC4ResultSet.getObject(int, Class) (JDBC4ResultSet.java:316-393). Only INTEGER and FLOAT storage convert; TEXT, BLOB, and NULL throw SQLException("Bad value for type <T>").
  • Removed the Short and Byte branches. They now throw SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"), as do all other unsupported target types.
  • Null target types now use xerial's message, requested type cannot be null.
  • Documented the target-type by storage-class matrix in the method Javadoc.
  • Added LibsqlResultSetGetObjectTest covering INTEGER, FLOAT, numeric text, non-numeric text, blobs, NULL, Short, Byte, unsupported target types, and the null target type. Updated the old test that expected numeric text to convert through getObject.

Contract decision

The xerial 3.53.4.0 gate wins, per #54's drop-in parity rule. Numeric wrapper targets convert only from INTEGER or FLOAT storage. This is a visible API reduction: Short and Byte were supported by this driver and are now rejected, and numeric text no longer converts to a numeric target through getObject(Class) (the plain numeric getters retain their SQLite coercion behavior). String, Boolean, BigDecimal, byte[], and the date/time targets keep their existing unrestricted conversions.

Evidence

Failing before the fix (new tests against the old implementation):

mise exec -- ./mvnw -pl libsql-jdbc -am -Dtest=LibsqlResultSetGetObjectTest -Dsurefire.failIfNoSpecifiedTests=false test
[ERROR] Tests run: 19, Failures: 11, Errors: 0, Skipped: 0
[ERROR] LibsqlResultSetGetObjectTest.numericTargetsRejectTextBlobAndNullStorage:72
Expecting code to raise a throwable.
[ERROR] LibsqlResultSetGetObjectTest.shortAndByteAreUnsupportedForEveryStorageClass:94
Expecting code to raise a throwable.
[ERROR] LibsqlResultSetGetObjectTest.unsupportedTargetTypeUsesXerialMessage:124
Expecting code to raise a throwable.
[ERROR] LibsqlResultSetGetObjectTest.unsupportedTargetTypeUsesXerialMessage:125
Expecting actual throwable to be exactly an instance of:
  java.sql.SQLFeatureNotSupportedException
but was:
  java.sql.SQLException: Cannot convert value of type class java.lang.Integer to java.util.UUID

Passing after the fix:

mise exec -- ./mvnw -pl libsql-jdbc -am -Dtest=LibsqlResultSetGetObjectTest,LibsqlResultSetTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests run: 37, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

mise exec -- ./mvnw -pl libsql-jdbc -am test
Tests run: 116, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

mise exec -- ./mvnw -pl libsql-conformance -am verify
Tests run: 117, Failures: 0, Errors: 0, Skipped: 1
BUILD SUCCESS

mise exec -- ./mvnw spotless:apply
BUILD SUCCESS (no files changed)

The conformance run includes the vendored xerial ResultSetTest against live sqld, including getObjectWithRequestedType.

Deviations

None. The Short/Byte removal and rejection of numeric text are xerial parity, not transport-driven deviations.

Closes #34. Part of #54.

Matches sqlite-jdbc 3.53.4.0: Integer, Long, Float, and Double convert only from INTEGER or FLOAT storage and throw 'Bad value for type X' otherwise, including NULL, TEXT, and BLOB. Short and Byte are no longer supported, and unsupported target types use SQLFeatureNotSupportedException. The target-type by storage-class matrix is documented on the method.
@magnusp
magnusp merged commit b25ddd9 into main Oct 21, 2026
5 checks passed
@magnusp
magnusp deleted the fix/34-getobject-storage-type branch October 21, 2026 22:57
magnusp added a commit that referenced this pull request Oct 22, 2026
Fixes #102

Contract: Hrana 3 carries only INTEGER, FLOAT, TEXT, and BLOB, so
date/time meaning is a local SQLite convention; `java.sql` defines the
getter surface; sqlite-jdbc is the behavioral reference, not the
contract.

Base: `168f69aff4cebe175699bf5b421decf6a49c3b09`
Head: `fb993bd06de73bef417193faa803949cd24fe8e8`

## What changed

`getDate`, `getTime`, and `getTimestamp` now switch on the `Value`
storage class instead of parsing `getString()`:

- INTEGER: milliseconds since the Unix epoch.
- FLOAT: Julian day.
- TEXT: SQLite date/time formats, including `yyyy-MM-dd HH:mm:ss.SSS`,
bare dates, bare times, optional fractional digits, and an optional
time-zone suffix.
- BLOB: `Bad value for type <target>`; NULL: `null`.

`LocalDate`, `LocalTime`, and `LocalDateTime` are built from the same
storage-class result, so `getObject(column, X.class)` agrees with the
corresponding `java.sql` getter. The #69 conversion table now documents
these SQLite conventions instead of text parsing.

## Evidence

- Red: `./mvnw -pl libsql-jdbc test -Dtest=LibsqlResultSetTest` at base
+ new tests -> 3 failures, all new cases
(`/tmp/xerial-parity-i102/lane-1-regression.log`).
- Green: same command at head -> 24 tests, 0 failures; with
`LibsqlResultSetGetObjectTest` -> 43 tests, 0 failures
(`/tmp/xerial-parity-i102/green-unit.log`).
- Lane 2 exact inputs: TEXT `2024-01-02 10:00:00` and INTEGER
`1700000000000` pass (`lane-2-specific.log`); xerial reference probe
output recorded in `reference-probe.log` (TEXT -> 2024-01-02; INTEGER ->
2023-11-14 in Europe/Stockholm).
- Lane 3: all valid datetime cases assert non-null across all six
targets (`lane-3-specific.log`).
- Lane 4 full module: BUILD SUCCESS, 238 tests
(`lane-4-module-suite.log`).
- Lane 5 conformance: BUILD SUCCESS, 123 tests, 1 pre-existing skip, no
new divergence (`lane-5-conformance.log`).
- Lane 6 xerial control: BUILD SUCCESS, 100 tests, 1 pre-existing skip
(`lane-6-control.log`).
- Lane 7 spotless:check: BUILD SUCCESS (`lane-7-spotless.log`).
- Lane 8 full reactor: BUILD SUCCESS (`lane-8-reactor.log`).
- Lane 9 `LibsqlResultSet*`: 116 tests, 0 failures
(`lane-9-specific.log`).
- Lane 10 package: BUILD SUCCESS (`lane-10-package.log`).
- Perf: `./mvnw -pl libsql-jdbc test` 3x interleaved. Trunk median 2.12
s, head median 2.15 s, +1.4% (within 10%).

## Divergences

- INTEGER is epoch milliseconds, not SQLite `unixepoch` seconds.
sqlite-jdbc's default `date_precision` is MILLISECONDS, and the issue's
`1700000000000` case requires it.
- REAL Julian days convert as a UTC instant and are then rendered in the
JVM default zone. xerial decomposes the Julian day into local calendar
fields; SQLite Julian days are UTC-based, and no conformance test
asserts REAL.
- Bare-time TEXT uses SQLite's 2000-01-01 default date, so
`getDate`/`LocalDate` return that date where xerial throws. This follows
SQLite's time-value rule.
- BLOB date/time targets throw `Bad value` instead of xerial's numeric
fallback; BLOB is not a SQLite datetime storage class.
- `getTime` returns a time-of-day, so `Time` does not carry the source
date as xerial's TEXT path does. JDBC defines `getTime` as the time
component.

Co-authored-by: Magnus Persson <magnus@magnusp.github.com>
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.

getObject(Class) ignores storage type for numeric targets

1 participant