From 47ea3f2426a4c60939c5fba99b5f6a70bd0d72b3 Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:22:08 +0200 Subject: [PATCH] fix(jdbc): throw SQLFeatureNotSupportedException and implement stream getters Replace the remaining UnsupportedOperationException paths in LibsqlResultSet and LibsqlDatabaseMetaData with SQLFeatureNotSupportedException, implement getBinaryStream/getCharacterStream and the read-only getBlob/getClob surface, reject non-empty type maps in getObject(Map), and reject a null findColumn label with SQLException. --- .../libsql/jdbc/LibsqlDatabaseMetaData.java | 29 +- .../magnusp/libsql/jdbc/LibsqlResultSet.java | 475 +++++++++++++----- .../jdbc/LibsqlDatabaseMetaDataTest.java | 31 ++ .../libsql/jdbc/LibsqlResultSetTest.java | 239 +++++++++ 4 files changed, 643 insertions(+), 131 deletions(-) diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java index 488fefc..48b4871 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java @@ -11,6 +11,7 @@ import java.sql.ResultSet; import java.sql.RowIdLifetime; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; import java.util.ArrayList; import java.util.Comparator; @@ -839,14 +840,14 @@ public Connection getConnection() throws SQLException { return connection; } - @Override public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { throw new UnsupportedOperationException(); } + @Override public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public ResultSet getSchemas() throws SQLException { return executeMetadataQuery("SELECT NULL AS TABLE_SCHEM, NULL AS TABLE_CATALOG WHERE 0"); } @Override public ResultSet getCatalogs() throws SQLException { return executeMetadataQuery("SELECT NULL AS TABLE_CAT WHERE 0"); } - @Override public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { throw new UnsupportedOperationException(); } + @Override public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { return executeMetadataQuery( @@ -910,14 +911,14 @@ public ResultSet getTypeInfo() throws SQLException { @Override public boolean deletesAreDetected(int type) throws SQLException { return false; } @Override public boolean insertsAreDetected(int type) throws SQLException { return false; } @Override public boolean supportsBatchUpdates() throws SQLException { return true; } - @Override public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { throw new UnsupportedOperationException(); } + @Override public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public boolean supportsSavepoints() throws SQLException { return false; } @Override public boolean supportsNamedParameters() throws SQLException { return false; } @Override public boolean supportsMultipleOpenResults() throws SQLException { return false; } @Override public boolean supportsGetGeneratedKeys() throws SQLException { return true; } - @Override public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { throw new UnsupportedOperationException(); } + @Override public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public boolean supportsResultSetHoldability(int holdability) throws SQLException { return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT; } @Override public int getResultSetHoldability() throws SQLException { return ResultSet.CLOSE_CURSORS_AT_COMMIT; } @Override public int getDatabaseMajorVersion() throws SQLException { @@ -937,10 +938,10 @@ public ResultSet getTypeInfo() throws SQLException { @Override public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { return getSchemas(); } @Override public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { return false; } @Override public boolean autoCommitFailureClosesAllResultSets() throws SQLException { return false; } - @Override public ResultSet getClientInfoProperties() throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { throw new UnsupportedOperationException(); } - @Override public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { throw new UnsupportedOperationException(); } + @Override public ResultSet getClientInfoProperties() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public boolean generatedKeyAlwaysReturned() throws SQLException { return false; } @Override diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSet.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSet.java index 9323016..b627ab1 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSet.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSet.java @@ -5,11 +5,16 @@ import com.github.magnusp.libsql.hrana.codec.StmtResult; import com.github.magnusp.libsql.hrana.codec.Value; +import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.OutputStream; import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; @@ -33,6 +38,7 @@ import java.time.LocalTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; @@ -50,6 +56,9 @@ * xerial. {@link #wasNull()} reports the last column read and throws while no column has been * read in the current row, including immediately before the first row and immediately after the * last row. When labels repeat, {@link #findColumn(String)} returns the first match. + * Unsupported optional features throw {@link SQLFeatureNotSupportedException}, and {@link + * #getBinaryStream}, {@link #getCharacterStream}, {@link #getBlob}, and {@link #getClob} + * materialize Hrana values locally. */ public class LibsqlResultSet implements ResultSet { @@ -678,9 +687,18 @@ public Object getObject(int columnIndex) throws SQLException { }; } + private static void rejectTypeMap(Map> map) throws SQLException { + if (map != null && !map.isEmpty()) { + throw new SQLFeatureNotSupportedException("Type maps are not supported"); + } + } + @Override public int findColumn(String columnLabel) throws SQLException { checkClosed(); + if (columnLabel == null) { + throw new SQLException("column label cannot be null"); + } if (columnNameToIndex == null) { columnNameToIndex = new HashMap<>(cols.size() * 2); for (int i = 0; i < cols.size(); i++) { @@ -828,17 +846,23 @@ public boolean isLast() throws SQLException { throw new SQLFeatureNotSupportedException("not supported by sqlite"); } - @Override public InputStream getAsciiStream(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override @Deprecated @SuppressWarnings("deprecation") public InputStream getUnicodeStream(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public InputStream getBinaryStream(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public InputStream getAsciiStream(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override @Deprecated @SuppressWarnings("deprecation") public InputStream getUnicodeStream(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public InputStream getBinaryStream(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } + @Override public InputStream getAsciiStream(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override @Deprecated @SuppressWarnings("deprecation") public InputStream getUnicodeStream(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public InputStream getBinaryStream(int columnIndex) throws SQLException { + byte[] bytes = getBytes(columnIndex); + return bytes == null ? null : new ByteArrayInputStream(bytes); + } + @Override public InputStream getAsciiStream(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override @Deprecated @SuppressWarnings("deprecation") public InputStream getUnicodeStream(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public InputStream getBinaryStream(String columnLabel) throws SQLException { return getBinaryStream(findColumn(columnLabel)); } @Override public SQLWarning getWarnings() throws SQLException { return null; } @Override public void clearWarnings() throws SQLException {} @Override public String getCursorName() throws SQLException { return null; } - @Override public Reader getCharacterStream(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Reader getCharacterStream(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } + @Override public Reader getCharacterStream(int columnIndex) throws SQLException { + String string = getString(columnIndex); + return string == null ? null : new StringReader(string); + } + @Override public Reader getCharacterStream(String columnLabel) throws SQLException { return getCharacterStream(findColumn(columnLabel)); } @Override public void beforeFirst() throws SQLException { throw new SQLException("Forward only"); } @Override public void afterLast() throws SQLException { throw new SQLException("Forward only"); } @Override public boolean first() throws SQLException { throw new SQLException("Forward only"); } @@ -859,124 +883,136 @@ public void setFetchSize(int rows) throws SQLException { @Override public boolean rowUpdated() throws SQLException { return false; } @Override public boolean rowInserted() throws SQLException { return false; } @Override public boolean rowDeleted() throws SQLException { return false; } - @Override public void updateNull(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBoolean(int columnIndex, boolean x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateByte(int columnIndex, byte x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateShort(int columnIndex, short x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateInt(int columnIndex, int x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateLong(int columnIndex, long x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateFloat(int columnIndex, float x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateDouble(int columnIndex, double x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateString(int columnIndex, String x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBytes(int columnIndex, byte[] x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateDate(int columnIndex, Date x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateTime(int columnIndex, Time x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateObject(int columnIndex, Object x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNull(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBoolean(String columnLabel, boolean x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateByte(String columnLabel, byte x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateShort(String columnLabel, short x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateInt(String columnLabel, int x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateLong(String columnLabel, long x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateFloat(String columnLabel, float x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateDouble(String columnLabel, double x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateString(String columnLabel, String x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBytes(String columnLabel, byte[] x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateDate(String columnLabel, Date x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateTime(String columnLabel, Time x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateObject(String columnLabel, Object x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void insertRow() throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateRow() throws SQLException { throw new UnsupportedOperationException(); } - @Override public void deleteRow() throws SQLException { throw new UnsupportedOperationException(); } - @Override public void refreshRow() throws SQLException { throw new UnsupportedOperationException(); } - @Override public void cancelRowUpdates() throws SQLException { throw new UnsupportedOperationException(); } - @Override public void moveToInsertRow() throws SQLException { throw new UnsupportedOperationException(); } - @Override public void moveToCurrentRow() throws SQLException { throw new UnsupportedOperationException(); } - @Override public Object getObject(int columnIndex, Map> map) throws SQLException { return getObject(columnIndex); } - @Override public Ref getRef(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Blob getBlob(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Clob getClob(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Array getArray(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Object getObject(String columnLabel, Map> map) throws SQLException { return getObject(columnLabel); } - @Override public Ref getRef(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Blob getBlob(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Clob getClob(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Array getArray(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } + @Override public void updateNull(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBoolean(int columnIndex, boolean x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateByte(int columnIndex, byte x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateShort(int columnIndex, short x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateInt(int columnIndex, int x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateLong(int columnIndex, long x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateFloat(int columnIndex, float x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateDouble(int columnIndex, double x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateString(int columnIndex, String x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBytes(int columnIndex, byte[] x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateDate(int columnIndex, Date x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateTime(int columnIndex, Time x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateObject(int columnIndex, Object x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNull(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBoolean(String columnLabel, boolean x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateByte(String columnLabel, byte x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateShort(String columnLabel, short x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateInt(String columnLabel, int x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateLong(String columnLabel, long x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateFloat(String columnLabel, float x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateDouble(String columnLabel, double x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateString(String columnLabel, String x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBytes(String columnLabel, byte[] x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateDate(String columnLabel, Date x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateTime(String columnLabel, Time x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateObject(String columnLabel, Object x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void insertRow() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateRow() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void deleteRow() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void refreshRow() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void cancelRowUpdates() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void moveToInsertRow() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void moveToCurrentRow() throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public Object getObject(int columnIndex, Map> map) throws SQLException { + rejectTypeMap(map); + return getObject(columnIndex); + } + @Override public Ref getRef(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public Blob getBlob(int columnIndex) throws SQLException { + byte[] bytes = getBytes(columnIndex); + return bytes == null ? null : new LibsqlBlob(bytes); + } + @Override public Clob getClob(int columnIndex) throws SQLException { + String string = getString(columnIndex); + return string == null ? null : new LibsqlClob(string); + } + @Override public Array getArray(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public Object getObject(String columnLabel, Map> map) throws SQLException { + rejectTypeMap(map); + return getObject(columnLabel); + } + @Override public Ref getRef(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public Blob getBlob(String columnLabel) throws SQLException { return getBlob(findColumn(columnLabel)); } + @Override public Clob getClob(String columnLabel) throws SQLException { return getClob(findColumn(columnLabel)); } + @Override public Array getArray(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public Date getDate(int columnIndex, Calendar cal) throws SQLException { return getDate(columnIndex); } @Override public Date getDate(String columnLabel, Calendar cal) throws SQLException { return getDate(columnLabel); } @Override public Time getTime(int columnIndex, Calendar cal) throws SQLException { return getTime(columnIndex); } @Override public Time getTime(String columnLabel, Calendar cal) throws SQLException { return getTime(columnLabel); } @Override public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { return getTimestamp(columnIndex); } @Override public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { return getTimestamp(columnLabel); } - @Override public URL getURL(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public URL getURL(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateRef(int columnIndex, Ref x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateRef(String columnLabel, Ref x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBlob(int columnIndex, Blob x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBlob(String columnLabel, Blob x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateClob(int columnIndex, Clob x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateClob(String columnLabel, Clob x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateArray(int columnIndex, Array x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateArray(String columnLabel, Array x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public RowId getRowId(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public RowId getRowId(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateRowId(int columnIndex, RowId x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateRowId(String columnLabel, RowId x) throws SQLException { throw new UnsupportedOperationException(); } + @Override public URL getURL(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public URL getURL(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateRef(int columnIndex, Ref x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateRef(String columnLabel, Ref x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBlob(int columnIndex, Blob x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBlob(String columnLabel, Blob x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateClob(int columnIndex, Clob x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateClob(String columnLabel, Clob x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateArray(int columnIndex, Array x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateArray(String columnLabel, Array x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public RowId getRowId(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public RowId getRowId(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateRowId(int columnIndex, RowId x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateRowId(String columnLabel, RowId x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public int getHoldability() throws SQLException { return CLOSE_CURSORS_AT_COMMIT; } - @Override public void updateNString(int columnIndex, String nString) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNString(String columnLabel, String nString) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNClob(int columnIndex, NClob nClob) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNClob(String columnLabel, NClob nClob) throws SQLException { throw new UnsupportedOperationException(); } - @Override public NClob getNClob(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public NClob getNClob(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public SQLXML getSQLXML(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public SQLXML getSQLXML(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { throw new UnsupportedOperationException(); } + @Override public void updateNString(int columnIndex, String nString) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNString(String columnLabel, String nString) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNClob(int columnIndex, NClob nClob) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNClob(String columnLabel, NClob nClob) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public NClob getNClob(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public NClob getNClob(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public SQLXML getSQLXML(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public SQLXML getSQLXML(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } @Override public String getNString(int columnIndex) throws SQLException { return getString(columnIndex); } @Override public String getNString(String columnLabel) throws SQLException { return getString(columnLabel); } - @Override public Reader getNCharacterStream(int columnIndex) throws SQLException { throw new UnsupportedOperationException(); } - @Override public Reader getNCharacterStream(String columnLabel) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateClob(int columnIndex, Reader reader) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateClob(String columnLabel, Reader reader) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNClob(int columnIndex, Reader reader) throws SQLException { throw new UnsupportedOperationException(); } - @Override public void updateNClob(String columnLabel, Reader reader) throws SQLException { throw new UnsupportedOperationException(); } + @Override public Reader getNCharacterStream(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public Reader getNCharacterStream(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateClob(int columnIndex, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateClob(String columnLabel, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNClob(int columnIndex, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } + @Override public void updateNClob(String columnLabel, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException("not implemented by SQLite JDBC driver"); } /** * Returns a column value converted to the requested Java type. @@ -1245,4 +1281,209 @@ public T unwrap(Class iface) throws SQLException { public boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(this); } + + /** Read-only {@link Blob} over the materialized bytes of one result column. */ + private static final class LibsqlBlob implements Blob { + + private byte[] bytes; + private boolean freed; + + private LibsqlBlob(byte[] bytes) { + this.bytes = bytes; + } + + private byte[] data() throws SQLException { + if (freed) { + throw new SQLException("Blob has been freed"); + } + return bytes; + } + + @Override + public long length() throws SQLException { + return data().length; + } + + @Override + public byte[] getBytes(long pos, int length) throws SQLException { + byte[] data = data(); + if (pos < 1) { + throw new SQLException("Invalid position: " + pos); + } + if (length < 0) { + throw new SQLException("Invalid length: " + length); + } + int start = (int) Math.min(pos - 1, data.length); + int end = Math.min(start + length, data.length); + return Arrays.copyOfRange(data, start, end); + } + + @Override + public InputStream getBinaryStream() throws SQLException { + return new ByteArrayInputStream(data()); + } + + @Override + public long position(byte[] pattern, long start) throws SQLException { + if (start < 1) { + throw new SQLException("Invalid position: " + start); + } + byte[] data = data(); + int from = (int) Math.min(start - 1, data.length); + for (int i = from; i + pattern.length <= data.length; i++) { + boolean match = true; + for (int j = 0; j < pattern.length; j++) { + if (data[i + j] != pattern[j]) { + match = false; + break; + } + } + if (match) { + return i + 1L; + } + } + return -1; + } + + @Override + public long position(Blob pattern, long start) throws SQLException { + return position(pattern.getBytes(1, (int) pattern.length()), start); + } + + @Override + public int setBytes(long pos, byte[] bytes) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob is read-only"); + } + + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob is read-only"); + } + + @Override + public OutputStream setBinaryStream(long pos) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob is read-only"); + } + + @Override + public void truncate(long len) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob is read-only"); + } + + @Override + public void free() throws SQLException { + freed = true; + bytes = null; + } + + @Override + public InputStream getBinaryStream(long pos, long length) throws SQLException { + if (length < 0) { + throw new SQLException("Invalid length: " + length); + } + return new ByteArrayInputStream(getBytes(pos, (int) Math.min(length, Integer.MAX_VALUE))); + } + } + + /** Read-only {@link Clob} over the materialized text of one result column. */ + private static final class LibsqlClob implements Clob { + + private String text; + private boolean freed; + + private LibsqlClob(String text) { + this.text = text; + } + + private String data() throws SQLException { + if (freed) { + throw new SQLException("Clob has been freed"); + } + return text; + } + + @Override + public long length() throws SQLException { + return data().length(); + } + + @Override + public String getSubString(long pos, int length) throws SQLException { + String data = data(); + if (pos < 1) { + throw new SQLException("Invalid position: " + pos); + } + if (length < 0) { + throw new SQLException("Invalid length: " + length); + } + int start = (int) Math.min(pos - 1, data.length()); + int end = Math.min(start + length, data.length()); + return data.substring(start, end); + } + + @Override + public Reader getCharacterStream() throws SQLException { + return new StringReader(data()); + } + + @Override + public InputStream getAsciiStream() throws SQLException { + return new ByteArrayInputStream(data().getBytes(StandardCharsets.US_ASCII)); + } + + @Override + public long position(String searchstr, long start) throws SQLException { + if (start < 1) { + throw new SQLException("Invalid position: " + start); + } + String data = data(); + int from = (int) Math.min(start - 1, data.length()); + int index = data.indexOf(searchstr, from); + return index < 0 ? -1 : index + 1L; + } + + @Override + public long position(Clob searchstr, long start) throws SQLException { + return position(searchstr.getSubString(1, (int) searchstr.length()), start); + } + + @Override + public int setString(long pos, String str) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob is read-only"); + } + + @Override + public int setString(long pos, String str, int offset, int len) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob is read-only"); + } + + @Override + public OutputStream setAsciiStream(long pos) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob is read-only"); + } + + @Override + public Writer setCharacterStream(long pos) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob is read-only"); + } + + @Override + public void truncate(long len) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob is read-only"); + } + + @Override + public void free() throws SQLException { + freed = true; + text = null; + } + + @Override + public Reader getCharacterStream(long pos, long length) throws SQLException { + if (length < 0) { + throw new SQLException("Invalid length: " + length); + } + return new StringReader(getSubString(pos, (int) Math.min(length, Integer.MAX_VALUE))); + } + } } diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java index 9e4960a..ba6fa72 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java @@ -13,6 +13,7 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; @@ -519,6 +520,36 @@ void getTypeInfoReturnsTheXerialTypeTable() throws SQLException { } } + @Test + void everyUnsupportedMethodThrowsSqlFeatureNotSupportedException() { + LibsqlDatabaseMetaData metadata = new LibsqlDatabaseMetaData(new RecordingConnection(emptyResult())); + + List calls = List.of( + new UnsupportedCall("getProcedures", () -> metadata.getProcedures(null, null, null)), + new UnsupportedCall("getProcedureColumns", () -> metadata.getProcedureColumns(null, null, null, null)), + new UnsupportedCall("getColumnPrivileges", () -> metadata.getColumnPrivileges(null, null, null, null)), + new UnsupportedCall("getTablePrivileges", () -> metadata.getTablePrivileges(null, null, null)), + new UnsupportedCall("getBestRowIdentifier", () -> metadata.getBestRowIdentifier( + null, null, null, DatabaseMetaData.bestRowSession, false)), + new UnsupportedCall("getVersionColumns", () -> metadata.getVersionColumns(null, null, null)), + new UnsupportedCall("getUDTs", () -> metadata.getUDTs(null, null, null, null)), + new UnsupportedCall("getSuperTypes", () -> metadata.getSuperTypes(null, null, null)), + new UnsupportedCall("getSuperTables", () -> metadata.getSuperTables(null, null, null)), + new UnsupportedCall("getAttributes", () -> metadata.getAttributes(null, null, null, null)), + new UnsupportedCall("getClientInfoProperties", metadata::getClientInfoProperties), + new UnsupportedCall("getFunctions", () -> metadata.getFunctions(null, null, null)), + new UnsupportedCall("getFunctionColumns", () -> metadata.getFunctionColumns(null, null, null, null)), + new UnsupportedCall("getPseudoColumns", () -> metadata.getPseudoColumns(null, null, null, null))); + + for (UnsupportedCall call : calls) { + assertThatThrownBy(call.call()) + .as(call.name()) + .isInstanceOf(SQLFeatureNotSupportedException.class); + } + } + + private record UnsupportedCall(String name, org.assertj.core.api.ThrowableAssert.ThrowingCallable call) {} + private static List columnNames(ResultSet rs) throws SQLException { List names = new ArrayList<>(); for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) { diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetTest.java index bcd870f..680e28d 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetTest.java @@ -6,14 +6,26 @@ import com.github.magnusp.libsql.hrana.codec.Value; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; import java.nio.charset.StandardCharsets; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.RowId; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.List; +import java.util.Map; import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; @@ -251,6 +263,233 @@ void realJulianDayFeedsEveryDateAndTimeGetter() throws SQLException { assertDateTimeGetters(rs, 1_704_153_600_000L); } + @Test + void binaryStreamContentMatchesGetBytesByteForByte() throws Exception { + byte[] payload = {0, 1, 2, 65, 127, -128, -1}; + LibsqlResultSet rs = resultSet(new Value.Blob(payload)); + assertThat(rs.next()).isTrue(); + + assertThat(rs.getBytes(1)).isEqualTo(payload); + assertThat(rs.getBinaryStream(1).readAllBytes()).isEqualTo(payload); + assertThat(rs.getBinaryStream("c1").readAllBytes()).isEqualTo(payload); + } + + @Test + void characterStreamContentMatchesGetStringCharacterForCharacter() throws Exception { + String text = "h\u00e9llo \u20ac \ud83d\ude00"; + LibsqlResultSet rs = resultSet(new Value.Text(text)); + assertThat(rs.next()).isTrue(); + + assertThat(rs.getString(1)).isEqualTo(text); + assertThat(read(rs.getCharacterStream(1))).isEqualTo(text); + assertThat(read(rs.getCharacterStream("c1"))).isEqualTo(text); + } + + @Test + void streamGettersReturnNullForSqlNull() throws Exception { + LibsqlResultSet rs = resultSet(Value.ofNull()); + assertThat(rs.next()).isTrue(); + + assertThat(rs.getBinaryStream(1)).isNull(); + assertThat(rs.getCharacterStream(1)).isNull(); + assertThat(rs.getBlob(1)).isNull(); + assertThat(rs.getClob(1)).isNull(); + } + + @Test + void blobAndClobExposeTheColumnValue() throws Exception { + byte[] payload = {9, 8, 7, 0, -1}; + String text = "clob text \u20ac"; + LibsqlResultSet rs = resultSet(new Value.Blob(payload), new Value.Text(text)); + assertThat(rs.next()).isTrue(); + + Blob blob = rs.getBlob(1); + assertThat(blob.length()).isEqualTo(payload.length); + assertThat(blob.getBytes(1, payload.length)).isEqualTo(payload); + assertThat(blob.getBinaryStream().readAllBytes()).isEqualTo(payload); + assertThat(blob.position(payload, 1)).isEqualTo(1); + assertThat(rs.getBlob("c1").getBytes(1, payload.length)).isEqualTo(payload); + + Clob clob = rs.getClob(2); + assertThat(clob.length()).isEqualTo(text.length()); + assertThat(clob.getSubString(1, text.length())).isEqualTo(text); + assertThat(read(clob.getCharacterStream())).isEqualTo(text); + assertThat(read(rs.getClob("c2").getCharacterStream())).isEqualTo(text); + + assertThatThrownBy(() -> blob.setBytes(1, new byte[] {1})) + .isInstanceOf(SQLFeatureNotSupportedException.class); + assertThatThrownBy(() -> clob.setString(1, "x")) + .isInstanceOf(SQLFeatureNotSupportedException.class); + } + + @Test + void getObjectWithMapRejectsNonEmptyMap() throws SQLException { + LibsqlResultSet rs = resultSet(new Value.Integer(42)); + assertThat(rs.next()).isTrue(); + Map> typeMap = Map.of("x", String.class); + + assertThat(rs.getObject(1, Map.of())).isEqualTo(42); + assertThat(rs.getObject("c1", Map.of())).isEqualTo(42); + assertThatThrownBy(() -> rs.getObject(1, typeMap)) + .isInstanceOf(SQLFeatureNotSupportedException.class) + .hasMessageContaining("Type maps"); + assertThatThrownBy(() -> rs.getObject("c1", typeMap)) + .isInstanceOf(SQLFeatureNotSupportedException.class) + .hasMessageContaining("Type maps"); + } + + @Test + void findColumnRejectsNullLabel() { + LibsqlResultSet rs = resultSet(new Value.Integer(1)); + + assertThatThrownBy(() -> rs.findColumn(null)) + .isInstanceOf(SQLException.class) + .isNotInstanceOf(NullPointerException.class) + .hasMessageContaining("null"); + } + + @Test + void everyUnsupportedMethodThrowsSqlFeatureNotSupportedException() throws SQLException { + LibsqlResultSet rs = resultSet(new Value.Integer(1)); + assertThat(rs.next()).isTrue(); + Ref ref = null; + Array array = null; + RowId rowId = null; + Blob blob = null; + Clob clob = null; + NClob nClob = null; + SQLXML sqlxml = null; + InputStream stream = null; + Reader reader = null; + + List calls = List.of( + new UnsupportedCall("getAsciiStream(int)", () -> rs.getAsciiStream(1)), + new UnsupportedCall("getAsciiStream(String)", () -> rs.getAsciiStream("c1")), + new UnsupportedCall("getUnicodeStream(int)", () -> rs.getUnicodeStream(1)), + new UnsupportedCall("getUnicodeStream(String)", () -> rs.getUnicodeStream("c1")), + new UnsupportedCall("getRef(int)", () -> rs.getRef(1)), + new UnsupportedCall("getRef(String)", () -> rs.getRef("c1")), + new UnsupportedCall("getArray(int)", () -> rs.getArray(1)), + new UnsupportedCall("getArray(String)", () -> rs.getArray("c1")), + new UnsupportedCall("getURL(int)", () -> rs.getURL(1)), + new UnsupportedCall("getURL(String)", () -> rs.getURL("c1")), + new UnsupportedCall("getRowId(int)", () -> rs.getRowId(1)), + new UnsupportedCall("getRowId(String)", () -> rs.getRowId("c1")), + new UnsupportedCall("getNClob(int)", () -> rs.getNClob(1)), + new UnsupportedCall("getNClob(String)", () -> rs.getNClob("c1")), + new UnsupportedCall("getSQLXML(int)", () -> rs.getSQLXML(1)), + new UnsupportedCall("getSQLXML(String)", () -> rs.getSQLXML("c1")), + new UnsupportedCall("getNCharacterStream(int)", () -> rs.getNCharacterStream(1)), + new UnsupportedCall("getNCharacterStream(String)", () -> rs.getNCharacterStream("c1")), + new UnsupportedCall("updateNull(int)", () -> rs.updateNull(1)), + new UnsupportedCall("updateBoolean(int)", () -> rs.updateBoolean(1, true)), + new UnsupportedCall("updateByte(int)", () -> rs.updateByte(1, (byte) 1)), + new UnsupportedCall("updateShort(int)", () -> rs.updateShort(1, (short) 1)), + new UnsupportedCall("updateInt(int)", () -> rs.updateInt(1, 1)), + new UnsupportedCall("updateLong(int)", () -> rs.updateLong(1, 1L)), + new UnsupportedCall("updateFloat(int)", () -> rs.updateFloat(1, 1.0f)), + new UnsupportedCall("updateDouble(int)", () -> rs.updateDouble(1, 1.0)), + new UnsupportedCall("updateBigDecimal(int)", () -> rs.updateBigDecimal(1, null)), + new UnsupportedCall("updateString(int)", () -> rs.updateString(1, "x")), + new UnsupportedCall("updateBytes(int)", () -> rs.updateBytes(1, new byte[] {1})), + new UnsupportedCall("updateDate(int)", () -> rs.updateDate(1, null)), + new UnsupportedCall("updateTime(int)", () -> rs.updateTime(1, null)), + new UnsupportedCall("updateTimestamp(int)", () -> rs.updateTimestamp(1, null)), + new UnsupportedCall("updateAsciiStream(int,int)", () -> rs.updateAsciiStream(1, stream, 1)), + new UnsupportedCall("updateBinaryStream(int,int)", () -> rs.updateBinaryStream(1, stream, 1)), + new UnsupportedCall("updateCharacterStream(int,int)", () -> rs.updateCharacterStream(1, reader, 1)), + new UnsupportedCall("updateObject(int,int)", () -> rs.updateObject(1, "x", 1)), + new UnsupportedCall("updateObject(int)", () -> rs.updateObject(1, "x")), + new UnsupportedCall("updateNull(String)", () -> rs.updateNull("c1")), + new UnsupportedCall("updateBoolean(String)", () -> rs.updateBoolean("c1", true)), + new UnsupportedCall("updateByte(String)", () -> rs.updateByte("c1", (byte) 1)), + new UnsupportedCall("updateShort(String)", () -> rs.updateShort("c1", (short) 1)), + new UnsupportedCall("updateInt(String)", () -> rs.updateInt("c1", 1)), + new UnsupportedCall("updateLong(String)", () -> rs.updateLong("c1", 1L)), + new UnsupportedCall("updateFloat(String)", () -> rs.updateFloat("c1", 1.0f)), + new UnsupportedCall("updateDouble(String)", () -> rs.updateDouble("c1", 1.0)), + new UnsupportedCall("updateBigDecimal(String)", () -> rs.updateBigDecimal("c1", null)), + new UnsupportedCall("updateString(String)", () -> rs.updateString("c1", "x")), + new UnsupportedCall("updateBytes(String)", () -> rs.updateBytes("c1", new byte[] {1})), + new UnsupportedCall("updateDate(String)", () -> rs.updateDate("c1", null)), + new UnsupportedCall("updateTime(String)", () -> rs.updateTime("c1", null)), + new UnsupportedCall("updateTimestamp(String)", () -> rs.updateTimestamp("c1", null)), + new UnsupportedCall("updateAsciiStream(String,int)", () -> rs.updateAsciiStream("c1", stream, 1)), + new UnsupportedCall("updateBinaryStream(String,int)", () -> rs.updateBinaryStream("c1", stream, 1)), + new UnsupportedCall("updateCharacterStream(String,int)", () -> rs.updateCharacterStream("c1", reader, 1)), + new UnsupportedCall("updateObject(String,int)", () -> rs.updateObject("c1", "x", 1)), + new UnsupportedCall("updateObject(String)", () -> rs.updateObject("c1", "x")), + new UnsupportedCall("insertRow", rs::insertRow), + new UnsupportedCall("updateRow", rs::updateRow), + new UnsupportedCall("deleteRow", rs::deleteRow), + new UnsupportedCall("refreshRow", rs::refreshRow), + new UnsupportedCall("cancelRowUpdates", rs::cancelRowUpdates), + new UnsupportedCall("moveToInsertRow", rs::moveToInsertRow), + new UnsupportedCall("moveToCurrentRow", rs::moveToCurrentRow), + new UnsupportedCall("updateRef(int)", () -> rs.updateRef(1, ref)), + new UnsupportedCall("updateRef(String)", () -> rs.updateRef("c1", ref)), + new UnsupportedCall("updateBlob(int)", () -> rs.updateBlob(1, blob)), + new UnsupportedCall("updateBlob(String)", () -> rs.updateBlob("c1", blob)), + new UnsupportedCall("updateClob(int)", () -> rs.updateClob(1, clob)), + new UnsupportedCall("updateClob(String)", () -> rs.updateClob("c1", clob)), + new UnsupportedCall("updateArray(int)", () -> rs.updateArray(1, array)), + new UnsupportedCall("updateArray(String)", () -> rs.updateArray("c1", array)), + new UnsupportedCall("updateRowId(int)", () -> rs.updateRowId(1, rowId)), + new UnsupportedCall("updateRowId(String)", () -> rs.updateRowId("c1", rowId)), + new UnsupportedCall("updateNString(int)", () -> rs.updateNString(1, "x")), + new UnsupportedCall("updateNString(String)", () -> rs.updateNString("c1", "x")), + new UnsupportedCall("updateNClob(int)", () -> rs.updateNClob(1, nClob)), + new UnsupportedCall("updateNClob(String)", () -> rs.updateNClob("c1", nClob)), + new UnsupportedCall("updateSQLXML(int)", () -> rs.updateSQLXML(1, sqlxml)), + new UnsupportedCall("updateSQLXML(String)", () -> rs.updateSQLXML("c1", sqlxml)), + new UnsupportedCall("updateNCharacterStream(int,long)", () -> rs.updateNCharacterStream(1, reader, 1L)), + new UnsupportedCall("updateNCharacterStream(String,long)", () -> rs.updateNCharacterStream("c1", reader, 1L)), + new UnsupportedCall("updateAsciiStream(int,long)", () -> rs.updateAsciiStream(1, stream, 1L)), + new UnsupportedCall("updateBinaryStream(int,long)", () -> rs.updateBinaryStream(1, stream, 1L)), + new UnsupportedCall("updateCharacterStream(int,long)", () -> rs.updateCharacterStream(1, reader, 1L)), + new UnsupportedCall("updateAsciiStream(String,long)", () -> rs.updateAsciiStream("c1", stream, 1L)), + new UnsupportedCall("updateBinaryStream(String,long)", () -> rs.updateBinaryStream("c1", stream, 1L)), + new UnsupportedCall("updateCharacterStream(String,long)", () -> rs.updateCharacterStream("c1", reader, 1L)), + new UnsupportedCall("updateBlob(int,InputStream,long)", () -> rs.updateBlob(1, stream, 1L)), + new UnsupportedCall("updateBlob(String,InputStream,long)", () -> rs.updateBlob("c1", stream, 1L)), + new UnsupportedCall("updateClob(int,Reader,long)", () -> rs.updateClob(1, reader, 1L)), + new UnsupportedCall("updateClob(String,Reader,long)", () -> rs.updateClob("c1", reader, 1L)), + new UnsupportedCall("updateNClob(int,Reader,long)", () -> rs.updateNClob(1, reader, 1L)), + new UnsupportedCall("updateNClob(String,Reader,long)", () -> rs.updateNClob("c1", reader, 1L)), + new UnsupportedCall("updateNCharacterStream(int)", () -> rs.updateNCharacterStream(1, reader)), + new UnsupportedCall("updateNCharacterStream(String)", () -> rs.updateNCharacterStream("c1", reader)), + new UnsupportedCall("updateAsciiStream(int)", () -> rs.updateAsciiStream(1, stream)), + new UnsupportedCall("updateBinaryStream(int)", () -> rs.updateBinaryStream(1, stream)), + new UnsupportedCall("updateCharacterStream(int)", () -> rs.updateCharacterStream(1, reader)), + new UnsupportedCall("updateAsciiStream(String)", () -> rs.updateAsciiStream("c1", stream)), + new UnsupportedCall("updateBinaryStream(String)", () -> rs.updateBinaryStream("c1", stream)), + new UnsupportedCall("updateCharacterStream(String)", () -> rs.updateCharacterStream("c1", reader)), + new UnsupportedCall("updateBlob(int,InputStream)", () -> rs.updateBlob(1, stream)), + new UnsupportedCall("updateBlob(String,InputStream)", () -> rs.updateBlob("c1", stream)), + new UnsupportedCall("updateClob(int,Reader)", () -> rs.updateClob(1, reader)), + new UnsupportedCall("updateClob(String,Reader)", () -> rs.updateClob("c1", reader)), + new UnsupportedCall("updateNClob(int,Reader)", () -> rs.updateNClob(1, reader)), + new UnsupportedCall("updateNClob(String,Reader)", () -> rs.updateNClob("c1", reader))); + + for (UnsupportedCall call : calls) { + assertThatThrownBy(call.call()) + .as(call.name()) + .isInstanceOf(SQLFeatureNotSupportedException.class); + } + } + + private record UnsupportedCall(String name, org.assertj.core.api.ThrowableAssert.ThrowingCallable call) {} + + private static String read(Reader reader) throws IOException { + StringBuilder text = new StringBuilder(); + char[] buffer = new char[256]; + int count; + while ((count = reader.read(buffer)) != -1) { + text.append(buffer, 0, count); + } + return text.toString(); + } + private static void assertDateTimeGetters(LibsqlResultSet rs, long epochMillis) throws SQLException { LocalDateTime expected = LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), ZoneId.systemDefault());