Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ Use the absolute path shown. A relative `DOCKER_CONFIG` is resolved against the

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.
- When Appendix B defines a conversion or mapping and sqlite-jdbc disagrees, 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.
- Table B-6 is a required-support matrix, not a rejection list. A representable declared column is not rejected because of its declared type; the stored value decides the conversion, matching sqlite-jdbc. Reject a declared type only for the five N/A types. Unimplemented getters still throw for any column.
- `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.

Expand Down
65 changes: 38 additions & 27 deletions docs/jdbc-appendix-b-compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,37 +202,48 @@ Table B-5 is observed through `setObject(int, Object, int)`. A listed pair must

## Table B-6, ResultSet getter conversions

Table B-6 is observed through each getter against its recommended declared type.
Table B-6 is the matrix of conversions the driver must support, observed through each getter against its recommended declared type. The driver does not reject a conversion because the table omits it. A representable declared column is not rejected because of its declared type, and the stored value decides whether the conversion succeeds, matching sqlite-jdbc. The five N/A types are still rejected before the getter runs.

| Appendix B row | Status | Reason | Test |
|---|---|---|---|
| `getByte` | supported | Reads a numeric, boolean or text value as a byte. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getShort` | supported | Reads a numeric, boolean or text value as a short. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getInt` | supported | Reads a numeric, boolean or text value as an int. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getLong` | supported | Reads a numeric, boolean or text value as a long. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getFloat` | supported | Reads a numeric, boolean or text value as a float. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getDouble` | supported | Reads a numeric, boolean or text value as a double. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBigDecimal` | supported | Reads a numeric, boolean or text value as BigDecimal. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBoolean` | emulated | Reads integer 0 or 1, and the text true or false, as Boolean. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getString` | supported | Reads text and the listed scalar types as String. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getNString` | supported | Reads text and the listed scalar types as String. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBytes` | supported | Reads binary values and the listed types as byte[]. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getDate` | emulated | Parses the supported text forms into java.sql.Date. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getTime` | emulated | Parses the supported text forms into java.sql.Time. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getTimestamp` | emulated | Parses the supported text forms into java.sql.Timestamp. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getAsciiStream` | emulated | Returns the text bytes as a US-ASCII stream. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBinaryStream` | supported | Returns the blob bytes as a stream. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getCharacterStream` | emulated | Returns a reader over the stored text or bytes. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getNCharacterStream` | emulated | Returns a reader over the stored text or bytes. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getClob` | emulated | Wraps stored text in java.sql.Clob. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getNClob` | emulated | Wraps stored text in java.sql.NClob. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBlob` | supported | Wraps stored bytes in java.sql.Blob. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getByte` | supported | Reads a numeric, boolean or text value as a byte. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getShort` | supported | Reads a numeric, boolean or text value as a short. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getInt` | supported | Reads a numeric, boolean or text value as an int. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getLong` | supported | Reads a numeric, boolean or text value as a long. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getFloat` | supported | Reads a numeric, boolean or text value as a float. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getDouble` | supported | Reads a numeric, boolean or text value as a double. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBigDecimal` | supported | Reads a numeric, boolean or text value as BigDecimal. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBoolean` | emulated | Reads integer 0 or 1, and the text true or false, as Boolean. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getString` | supported | Reads any stored value as String; blobs decode as UTF-8. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getNString` | supported | Reads any stored value as String; blobs decode as UTF-8. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBytes` | supported | Reads any stored value as byte[]; text encodes as UTF-8. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getDate` | emulated | Parses the supported text forms into java.sql.Date. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getTime` | emulated | Parses the supported text forms into java.sql.Time. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getTimestamp` | emulated | Parses the supported text forms into java.sql.Timestamp. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getAsciiStream` | emulated | Returns any stored value as a US-ASCII stream. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBinaryStream` | supported | Returns any stored value as a byte stream. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getCharacterStream` | emulated | Returns a reader over the stored text or bytes. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getNCharacterStream` | emulated | Returns a reader over the stored text or bytes. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getClob` | emulated | Wraps any stored value in java.sql.Clob. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getNClob` | emulated | Wraps any stored value in java.sql.NClob. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getBlob` | supported | Wraps any stored value in java.sql.Blob. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getArray` | N/A | SQLite has no interoperable representation. The driver throws SQLFeatureNotSupportedException. | `Jdbc4AppendixBProfileTest#everyUnsupportedDeclaredTypeThrowsFeatureNotSupported` |
| `getRef` | N/A | SQLite has no interoperable representation. The driver throws SQLFeatureNotSupportedException. | `Jdbc4AppendixBProfileTest#everyUnsupportedDeclaredTypeThrowsFeatureNotSupported` |
| `getURL` | emulated | Parses stored text into java.net.URL. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getObject` | supported | Returns the Table B-3 object type for each supported declared type. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getRowId` | emulated | Wraps stored text or bytes in java.sql.RowId. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getSQLXML` | emulated | Wraps stored text in java.sql.SQLXML. | `Jdbc4AppendixBReadTest#b6GetterHonoursSupportedJdbcTypes`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getURL` | emulated | Parses stored text into java.net.URL. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getObject` | supported | Returns the Table B-3 object type for each supported declared type. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getRowId` | emulated | Wraps any stored value in java.sql.RowId. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |
| `getSQLXML` | emulated | Wraps any stored value in java.sql.SQLXML. | `Jdbc4AppendixBReadTest#b6RequiredConversionsAndPermissiveReads`, `Jdbc4AppendixBTypeMappingIT#b6GetterReadsRecommendedTypeFromRealDdl` |

### Read conversions beyond Table B-6

Table B-6 defines the conversions a driver must support; it does not require the driver to reject
the rest. The driver therefore does not reject a representable declared column because of its
declared type, and the stored value decides the conversion. A declared `BLOB` reads through
`getBytes`, `getString` and `getBinaryStream`, as it does in sqlite-jdbc. A conversion the stored
value cannot satisfy throws `SQLException`. Only the five N/A declared types are rejected because of
the declared type; unimplemented getters (`getArray`, `getRef`, `getUnicodeStream`) still throw
`SQLFeatureNotSupportedException` for any column. Regression coverage is
`Jdbc4AppendixBReadTest#b6UnlistedConversionsReadTheStoredValue`.

## Documented exception

Expand All @@ -242,4 +253,4 @@ The fallback is covered by `Jdbc4AppendixBProfileTest#expressionFallbackStaysPer

## Conformance quarantines

The xerial conformance suite keeps its selected tests except for four methods whose expectations Appendix B replaces. The list and the reason for each one are in [`../libsql-conformance/README.md`](../libsql-conformance/README.md). They still run in the `xerial-control` profile.
The xerial conformance suite keeps its selected tests except for three methods whose expectations Appendix B replaces. The list and the reason for each one are in [`../libsql-conformance/README.md`](../libsql-conformance/README.md). They still run in the `xerial-control` profile.
8 changes: 3 additions & 5 deletions libsql-conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,27 @@ with:
./mvnw -pl libsql-conformance -am verify
```

Last green result: 270 tests, 7 skipped, with `SavepointTest` included. The
count excludes the four Appendix B quarantines below and includes the two
Last green result: 271 tests, 7 skipped, with `SavepointTest` included. The
count excludes the three 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
JDBC 4.3 Appendix B is the oracle. Three 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ 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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.magnusp.libsql.itest;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
Expand Down Expand Up @@ -298,6 +299,27 @@ record GetterCell(
new GetterCell("getRowId", "ROWID", TEXT_ROWID, ResultSet::getRowId, RowId.class),
new GetterCell("getSQLXML", "SQLXML", TEXT_XML, ResultSet::getSQLXML, SQLXML.class));

@Test
void b6DeclaredBlobReadsThroughByteAndTextGetters() throws Exception {
String table = table("b6blob");
try (Connection connection = connect(); Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + table + " (v BLOB)");
try (PreparedStatement insert =
connection.prepareStatement("INSERT INTO " + table + " (v) VALUES (?)")) {
insert.setBytes(1, BYTES_ABC);
insert.executeUpdate();
}
try (Statement query = connection.createStatement();
ResultSet resultSet = query.executeQuery("SELECT v FROM " + table)) {
assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getBytes(1)).isEqualTo(BYTES_ABC);
assertThat(resultSet.getString(1)).isEqualTo("abc");
assertThat(resultSet.getBinaryStream(1).readAllBytes()).isEqualTo(BYTES_ABC);
assertThat(resultSet.getBlob(1).getBytes(1, BYTES_ABC.length)).isEqualTo(BYTES_ABC);
}
}
}

@ParameterizedTest(name = "B-6 {0} reads its recommended {1}")
@MethodSource("b6Cells")
void b6GetterReadsRecommendedTypeFromRealDdl(GetterCell cell) throws SQLException {
Expand Down
Loading
Loading