Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
aafd14c
Add type mapping doc
magnusp Oct 24, 2026
3e8f582
Update to 4.3
magnusp Oct 25, 2026
6a9e74a
test(jdbc): add the JDBC 4.3 Appendix B oracle suite
magnusp Oct 25, 2026
5d4e6aa
feat(jdbc): add the declared JDBC type registry
magnusp Oct 25, 2026
7fa7c5c
feat(jdbc): resolve metadata and getObject from the declared type
magnusp Oct 25, 2026
4c98a63
test(jdbc): pin declared types and boolean text parsing
magnusp Oct 25, 2026
2aab9bd
perf(jdbc): resolve each column's declared type once per result set
magnusp Oct 25, 2026
594727c
perf(jdbc): cache the current row values for the column getters
magnusp Oct 25, 2026
d900f63
fix(jdbc): keep sqlite-jdbc metadata for SQLite type aliases
magnusp Oct 25, 2026
ace98f7
feat(jdbc): add the JDBC value converter
magnusp Oct 25, 2026
4ae213a
feat(jdbc): convert setObject values to the target JDBC type
magnusp Oct 25, 2026
0ea436c
test(jdbc): pin setObject conversions per target family
magnusp Oct 25, 2026
bc1039e
feat(jdbc): add NClob, SQLXML, and RowId wrappers
magnusp Oct 25, 2026
d9bbb22
feat(jdbc): read URL, LOB, row id, and NCHAR result columns
magnusp Oct 25, 2026
58ef0e3
feat(jdbc): bind SQLXML and RowId parameters
magnusp Oct 25, 2026
4067d42
test(jdbc): cover wrapper free, length, and stream reads
magnusp Oct 25, 2026
9e8c42d
feat(jdbc): read timezone-aware temporal columns
magnusp Oct 25, 2026
cf6a031
test(jdbc): cover offset parsing and declared offset reads
magnusp Oct 25, 2026
3d11c0c
test(jdbc): verify declared offset reads against sqld
magnusp Oct 25, 2026
bb72ef6
feat(jdbc): record n/a types and table b-6 getter support
magnusp Oct 25, 2026
ec28549
feat(jdbc): reject unsupported reads and unlisted getter conversions
magnusp Oct 25, 2026
2677afb
feat(jdbc): reject unsupported setobject targets
magnusp Oct 25, 2026
068e91d
test(jdbc): assert n/a rows against sqld
magnusp Oct 25, 2026
ab67b9f
fix(itests): supply real rowid and sqlxml fixtures
magnusp Oct 25, 2026
6da78cb
test(conformance): quarantine xerial tests replaced by appendix b
magnusp Oct 25, 2026
234186d
docs(jdbc): publish the appendix b compliance profile
magnusp Oct 25, 2026
0a8ec03
test(itests): assert the nclob wrapper and its content
magnusp Oct 25, 2026
c3190ff
docs(conformance): note the checks the method quarantines drop
magnusp Oct 25, 2026
0861960
docs(jdbc): point setter n/a rows at setter tests
magnusp Oct 25, 2026
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
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" \

Use the absolute path shown. A relative `DOCKER_CONFIG` is resolved against the fork's working directory, not the shell's.

### JDBC 4.3 Appendix B policy

JDBC 4.3 Appendix B is the oracle for type mapping. The source is [`vendor/JDBC4-TYPE-MAPPING.md`](vendor/JDBC4-TYPE-MAPPING.md).

- When Appendix B and sqlite-jdbc disagree, Appendix B wins. A conformance test that encodes the xerial behavior is quarantined in `libsql-conformance`, not copied.
- Keep sqlite-jdbc parity only where Appendix B is silent. Parity never overrides a defined Appendix B conversion.
- `ARRAY`, `STRUCT`, `REF`, `JAVA_OBJECT`, and `DISTINCT` are N/A. The driver throws `SQLFeatureNotSupportedException` and does not emulate them.
- An expression column has no declared type. Its storage-class fallback is a documented exception in [`docs/jdbc-appendix-b-compliance.md`](docs/jdbc-appendix-b-compliance.md), not a supported Appendix B row.

The compliance profile is [`docs/jdbc-appendix-b-compliance.md`](docs/jdbc-appendix-b-compliance.md). Run the commands in [`README.md`](README.md) before changing a type mapping.

---

## 5. Coding Standards & Conventions
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,41 @@ mise exec -- ./mvnw clean verify
mise exec -- ./mvnw spotless:apply
```

### JDBC 4.3 Appendix B checks

Run the Appendix B unit suite.

```bash
./mvnw -q -pl libsql-jdbc -Dtest='Jdbc4AppendixB*' -Dsurefire.failIfNoSpecifiedTests=false test
```

Run the full `libsql-jdbc` unit suite.

```bash
./mvnw -q -pl libsql-jdbc test
```

Run the Appendix B server suite. The install step puts the current driver in the local repository. Under the nono sandbox, the Docker config path is required.

```bash
./mvnw -q -DskipTests install -pl libsql-hrana-codec,libsql-client,libsql-jdbc
DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw -pl libsql-itests -Dit.test=Jdbc4AppendixBTypeMappingIT -Dtest=NoSuchTest -Dsurefire.failIfNoSpecifiedTests=false verify
```

Run the xerial conformance suite.

```bash
./mvnw -pl libsql-conformance -am verify
```

Run the full gate.

```bash
DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw clean verify
```

The compliance profile is in [`docs/jdbc-appendix-b-compliance.md`](docs/jdbc-appendix-b-compliance.md).

## Contributing

We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and development workflow, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards.
Expand Down
245 changes: 245 additions & 0 deletions docs/jdbc-appendix-b-compliance.md

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions libsql-conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,32 @@ with:
./mvnw -pl libsql-conformance -am verify
```

Last green result: 272 tests, 7 skipped, with `SavepointTest` included. The
parity queue (#54) closed at 268 tests, 7 skipped.
Last green result: 270 tests, 7 skipped, with `SavepointTest` included. The
count excludes the four Appendix B quarantines below and includes the two
harness tests for the quarantine.

### Appendix B quarantines

JDBC 4.3 Appendix B is the oracle. Four selected xerial test methods assert
behavior that Appendix B replaces. They stay in the vendored tree and still
run in the `xerial-control` profile, but `XerialQuarantine` excludes them from
the libSQL run. The full type profile is in
[`../docs/jdbc-appendix-b-compliance.md`](../docs/jdbc-appendix-b-compliance.md).

| Test | Why the xerial expectation is quarantined |
|---|---|
| `org.sqlite.StatementTest#bytesTest` | xerial allows `getBytes` on a declared `BLOB`. Table B-6 lists `getBytes` only for `BINARY`, `VARBINARY`, and `LONGVARBINARY`. |
| `org.sqlite.RSMetaDataTest#columnTypes` | xerial reports a declared `NCHAR` as `Types.CHAR`. Appendix B defines `NCHAR` as its own JDBC type, so the driver reports `Types.NCHAR`. |
| `org.sqlite.RSMetaDataTest#testGetColumnClassName` | xerial reports a declared `BLOB` as `java.lang.Object`. Table B-3 maps `BLOB` to `java.sql.Blob`. |
| `org.sqlite.PrepStmtTest#getObject` | xerial returns an `Integer` for `getObject` on a declared `BIT`. Table B-3 maps `BIT` to `Boolean`. |

The exclusion is method-level, so each method also loses checks that do not
conflict with Appendix B:

- `StatementTest#bytesTest` also loses its BLOB `setBytes`/`getBytes` round trip.
- `RSMetaDataTest#columnTypes` also checks other column types that do not conflict, including the INTEGER family, text, numeric, boolean, date and timestamp types, plus DECIMAL precision and scale.
- `RSMetaDataTest#testGetColumnClassName` also checks the INTEGER, VARCHAR, and DOUBLE class names.
- `PrepStmtTest#getObject` also checks other `getObject` rows (INTEGER, FLOAT, untyped, VARCHAR and BLOB columns) and the typed getters above them.

### Three-layer contract

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.support.descriptor.MethodSource;

/**
* Exercises the conformance harness itself, without a running sqld container where possible.
Expand Down Expand Up @@ -63,6 +65,54 @@ void fileMemoryUriIsNotRetained() {
assertThat(memoryMode.retained()).isFalse();
}

@Test
void quarantineExcludesOnlyTheAppendixBConflicts() {
XerialQuarantine quarantine = new XerialQuarantine();

for (XerialQuarantine.Quarantined quarantined : XerialQuarantine.QUARANTINED) {
assertThat(
quarantine
.apply(
descriptor(
quarantined.className(),
quarantined.methodName()))
.excluded())
.as("%s#%s", quarantined.className(), quarantined.methodName())
.isTrue();
}

assertThat(quarantine.apply(descriptor("org.sqlite.ResultSetTest", "next")).included())
.as("an unconflicted xerial test")
.isTrue();
}

@Test
void quarantineIsInactiveDuringTheXerialControlRun() {
String previous = System.getProperty(XerialControl.ENABLED_PROPERTY);
System.setProperty(XerialControl.ENABLED_PROPERTY, "xerial");
try {
XerialQuarantine quarantine = new XerialQuarantine();
for (XerialQuarantine.Quarantined quarantined : XerialQuarantine.QUARANTINED) {
assertThat(
quarantine
.apply(
descriptor(
quarantined.className(),
quarantined.methodName()))
.included())
.as("control run keeps %s#%s", quarantined.className(),
quarantined.methodName())
.isTrue();
}
} finally {
if (previous == null) {
System.clearProperty(XerialControl.ENABLED_PROPERTY);
} else {
System.setProperty(XerialControl.ENABLED_PROPERTY, previous);
}
}
}

@Test
void queryStringIsPartOfTheFileNamespace() {
XerialConformanceDriver.Target readOnly =
Expand Down Expand Up @@ -163,6 +213,19 @@ void stopReportsASwallowedCleanupFailure() {
}
}

private static TestDescriptor descriptor(String className, String methodName) {
MethodSource source = MethodSource.from(className, methodName);
return (TestDescriptor)
Proxy.newProxyInstance(
TestDescriptor.class.getClassLoader(),
new Class<?>[] {TestDescriptor.class},
(proxy, method, args) ->
switch (method.getName()) {
case "getSource" -> java.util.Optional.of(source);
default -> null;
});
}

private static Connection wrap(Connection connection, String namespace) {
return wrap(connection, namespace, ignored -> {});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.github.magnusp.libsql.conformance;

import java.util.List;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.support.descriptor.MethodSource;
import org.junit.platform.launcher.PostDiscoveryFilter;

/**
* Quarantines the xerial tests whose expectations Appendix B replaces.
*
* <p>These tests assert sqlite-jdbc behavior that JDBC 4.3 Appendix B defines differently. They
* stay in the vendored tree and still run in the {@code xerial-control} profile, where the
* reference driver is the subject. They are excluded only from the libSQL run, and the reason is
* kept here so the exclusion cannot become an unexplained skip.
*/
public final class XerialQuarantine implements PostDiscoveryFilter {

/** One upstream test method whose xerial expectation conflicts with Appendix B. */
record Quarantined(String className, String methodName, String reason) {}

static final List<Quarantined> QUARANTINED =
List.of(
new Quarantined(
"org.sqlite.StatementTest",
"bytesTest",
"xerial allows getBytes on a declared BLOB; Table B-6 lists getBytes"
+ " only for BINARY, VARBINARY and LONGVARBINARY"),
new Quarantined(
"org.sqlite.RSMetaDataTest",
"columnTypes",
"xerial reports a declared NCHAR as Types.CHAR; Appendix B defines"
+ " NCHAR as its own JDBC type, so the driver reports Types.NCHAR"),
new Quarantined(
"org.sqlite.RSMetaDataTest",
"testGetColumnClassName",
"xerial reports a declared BLOB as java.lang.Object; Table B-3 maps"
+ " BLOB to java.sql.Blob"),
new Quarantined(
"org.sqlite.PrepStmtTest",
"getObject",
"xerial returns an Integer for getObject on a declared BIT; Table B-3"
+ " maps BIT to Boolean"));

@Override
public FilterResult apply(TestDescriptor descriptor) {
if (XerialControl.isEnabled()
|| !(descriptor.getSource().orElse(null) instanceof MethodSource source)) {
return FilterResult.included("Appendix B quarantine is inactive for this run");
}
for (Quarantined quarantined : QUARANTINED) {
if (quarantined.className().equals(source.getClassName())
&& quarantined.methodName().equals(source.getMethodName())) {
return FilterResult.excluded(quarantined.reason());
}
}
return FilterResult.included("not quarantined");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.github.magnusp.libsql.conformance.XerialQuarantine
Loading
Loading