Merge 'java/bindings: Add support for creating statement ' from Kim Seon Woo

## Purpose of this PR
- Add support for `createStatement` in `JDBC4Connection`
- Following works can use this statement to execute queries
## Changes
- Implement `createStatement` for `JDBC4Connection`
- Add `JDBC4Statement`
## References
- https://github.com/tursodatabase/limbo/issues/615

Closes #693
This commit is contained in:
Pekka Enberg
2025-01-15 09:09:42 +02:00
6 changed files with 533 additions and 19 deletions

View File

@@ -4,6 +4,7 @@ import org.github.tursodatabase.core.AbstractDB;
import org.github.tursodatabase.core.LimboDB;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
@@ -61,4 +62,43 @@ public abstract class LimboConnection implements Connection {
database.open(0);
return database;
}
protected void checkOpen() throws SQLException {
if (isClosed()) throw new SQLException("database connection closed");
}
@Override
public void close() throws SQLException {
if (isClosed()) return;
database.close();
}
@Override
public boolean isClosed() throws SQLException {
return database.isClosed();
}
// TODO: check whether this is still valid for limbo
/**
* Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are
* supported by the SQLite interface. Supported settings are:
*
* <ul>
* <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}
* <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})
* <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
* </ul>
*
* @param resultSetType the type setting.
* @param resultSetConcurrency the concurrency setting.
* @param resultSetHoldability the holdability setting.
*/
protected void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
throw new SQLException("SQLite only supports CONCUR_READ_ONLY cursors");
if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT)
throw new SQLException("SQLite only supports closing cursors at commit");
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2007 David Crawshaw <david@zentus.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package org.github.tursodatabase.core;
public class Codes {
/** Successful result */
public static final int SQLITE_OK = 0;
/** SQL error or missing database */
public static final int SQLITE_ERROR = 1;
/** An internal logic error in SQLite */
public static final int SQLITE_INTERNAL = 2;
/** Access permission denied */
public static final int SQLITE_PERM = 3;
/** Callback routine requested an abort */
public static final int SQLITE_ABORT = 4;
/** The database file is locked */
public static final int SQLITE_BUSY = 5;
/** A table in the database is locked */
public static final int SQLITE_LOCKED = 6;
/** A malloc() failed */
public static final int SQLITE_NOMEM = 7;
/** Attempt to write a readonly database */
public static final int SQLITE_READONLY = 8;
/** Operation terminated by sqlite_interrupt() */
public static final int SQLITE_INTERRUPT = 9;
/** Some kind of disk I/O error occurred */
public static final int SQLITE_IOERR = 10;
/** The database disk image is malformed */
public static final int SQLITE_CORRUPT = 11;
/** (Internal Only) Table or record not found */
public static final int SQLITE_NOTFOUND = 12;
/** Insertion failed because database is full */
public static final int SQLITE_FULL = 13;
/** Unable to open the database file */
public static final int SQLITE_CANTOPEN = 14;
/** Database lock protocol error */
public static final int SQLITE_PROTOCOL = 15;
/** (Internal Only) Database table is empty */
public static final int SQLITE_EMPTY = 16;
/** The database schema changed */
public static final int SQLITE_SCHEMA = 17;
/** Too much data for one row of a table */
public static final int SQLITE_TOOBIG = 18;
/** Abort due to constraint violation */
public static final int SQLITE_CONSTRAINT = 19;
/** Data type mismatch */
public static final int SQLITE_MISMATCH = 20;
/** Library used incorrectly */
public static final int SQLITE_MISUSE = 21;
/** Uses OS features not supported on host */
public static final int SQLITE_NOLFS = 22;
/** Authorization denied */
public static final int SQLITE_AUTH = 23;
/** sqlite_step() has another row ready */
public static final int SQLITE_ROW = 100;
/** sqlite_step() has finished executing */
public static final int SQLITE_DONE = 101;
// types returned by sqlite3_column_type()
public static final int SQLITE_INTEGER = 1;
public static final int SQLITE_FLOAT = 2;
public static final int SQLITE_TEXT = 3;
public static final int SQLITE_BLOB = 4;
public static final int SQLITE_NULL = 5;
}

View File

@@ -1,5 +1,26 @@
package org.github.tursodatabase.core;
// TODO: add fields and methods
public class CoreStatement {
import org.github.tursodatabase.LimboConnection;
import java.sql.SQLException;
public abstract class CoreStatement {
private final LimboConnection connection;
protected CoreStatement(LimboConnection connection) {
this.connection = connection;
}
protected void internalClose() throws SQLException {
// TODO
}
protected void clearGeneratedKeys() throws SQLException {
// TODO
}
protected void updateGeneratedKeys() throws SQLException {
// TODO
}
}

View File

@@ -16,10 +16,25 @@ public class JDBC4Connection extends LimboConnection {
}
@Override
@SkipNullableCheck
public Statement createStatement() throws SQLException {
// TODO
return null;
return createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT
);
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return createStatement(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
checkOpen();
checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
return new JDBC4Statement(this);
}
@Override
@@ -127,13 +142,6 @@ public class JDBC4Connection extends LimboConnection {
// TODO
}
@Override
@SkipNullableCheck
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
@@ -193,13 +201,6 @@ public class JDBC4Connection extends LimboConnection {
// TODO
}
@Override
@SkipNullableCheck
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {

View File

@@ -0,0 +1,290 @@
package org.github.tursodatabase.jdbc4;
import org.github.tursodatabase.LimboConnection;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.CoreStatement;
import java.sql.*;
/**
* Implementation of the {@link Statement} interface for JDBC 4.
*/
public class JDBC4Statement extends CoreStatement implements Statement {
private boolean closed;
private boolean closeOnCompletion;
private final int resultSetType;
private final int resultSetConcurrency;
private final int resultSetHoldability;
public JDBC4Statement(LimboConnection connection) {
this(connection, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
public JDBC4Statement(LimboConnection connection, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
super(connection);
this.resultSetType = resultSetType;
this.resultSetConcurrency = resultSetConcurrency;
this.resultSetHoldability = resultSetHoldability;
}
@Override
@SkipNullableCheck
public ResultSet executeQuery(String sql) throws SQLException {
// TODO
return null;
}
@Override
public int executeUpdate(String sql) throws SQLException {
// TODO
return 0;
}
@Override
public void close() throws SQLException {
clearGeneratedKeys();
internalClose();
closed = true;
}
@Override
public int getMaxFieldSize() throws SQLException {
// TODO
return 0;
}
@Override
public void setMaxFieldSize(int max) throws SQLException {
// TODO
}
@Override
public int getMaxRows() throws SQLException {
// TODO
return 0;
}
@Override
public void setMaxRows(int max) throws SQLException {
// TODO
}
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
// TODO
}
@Override
public int getQueryTimeout() throws SQLException {
// TODO
return 0;
}
@Override
public void setQueryTimeout(int seconds) throws SQLException {
// TODO
}
@Override
public void cancel() throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public SQLWarning getWarnings() throws SQLException {
// TODO
return null;
}
@Override
public void clearWarnings() throws SQLException {
// TODO
}
@Override
public void setCursorName(String name) throws SQLException {
// TODO
}
@Override
public boolean execute(String sql) throws SQLException {
// TODO
return false;
}
@Override
@SkipNullableCheck
public ResultSet getResultSet() throws SQLException {
// TODO
return null;
}
@Override
public int getUpdateCount() throws SQLException {
// TODO
return 0;
}
@Override
public boolean getMoreResults() throws SQLException {
// TODO
return false;
}
@Override
public void setFetchDirection(int direction) throws SQLException {
// TODO
}
@Override
public int getFetchDirection() throws SQLException {
// TODO
return 0;
}
@Override
public void setFetchSize(int rows) throws SQLException {
// TODO
}
@Override
public int getFetchSize() throws SQLException {
// TODO
return 0;
}
@Override
public int getResultSetConcurrency() {
return resultSetConcurrency;
}
@Override
public int getResultSetType() {
return resultSetType;
}
@Override
public void addBatch(String sql) throws SQLException {
// TODO
}
@Override
public void clearBatch() throws SQLException {
// TODO
}
@Override
public int[] executeBatch() throws SQLException {
// TODO
return new int[0];
}
@Override
@SkipNullableCheck
public Connection getConnection() throws SQLException {
// TODO
return null;
}
@Override
public boolean getMoreResults(int current) throws SQLException {
// TODO
return false;
}
@Override
@SkipNullableCheck
public ResultSet getGeneratedKeys() throws SQLException {
// TODO
return null;
}
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return 0;
}
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
// TODO
return 0;
}
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
// TODO
return 0;
}
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return false;
}
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
// TODO
return false;
}
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
// TODO
return false;
}
@Override
public int getResultSetHoldability() {
return resultSetHoldability;
}
@Override
public boolean isClosed() throws SQLException {
// TODO
return false;
}
@Override
public void setPoolable(boolean poolable) throws SQLException {
// TODO
}
@Override
public boolean isPoolable() throws SQLException {
// TODO
return false;
}
@Override
public void closeOnCompletion() throws SQLException {
if (closed) throw new SQLException("statement is closed");
closeOnCompletion = true;
}
/**
* Indicates whether the statement should be closed automatically when all its dependent result sets are closed.
*/
@Override
public boolean isCloseOnCompletion() throws SQLException {
if (closed) throw new SQLException("statement is closed");
return closeOnCompletion;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
}

View File

@@ -0,0 +1,58 @@
package org.github.tursodatabase.jdbc4;
import org.github.tursodatabase.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
class JDBC4ConnectionTest {
private JDBC4Connection connection;
@BeforeEach
void setUp() throws Exception {
String fileUrl = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + fileUrl;
connection = new JDBC4Connection(url, fileUrl, new Properties());
}
@Test
void test_create_statement_valid() throws SQLException {
Statement stmt = connection.createStatement();
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());
}
@Test
void test_create_statement_with_type_and_concurrency_valid() throws SQLException {
Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
}
@Test
void test_create_statement_with_all_params_valid() throws SQLException {
Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());
}
@Test
void test_create_statement_invalid() {
assertThrows(SQLException.class, () -> {
connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1);
});
}
}