diff --git a/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java b/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java
index 98f0ad04b..de1a5228e 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java
@@ -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:
+ *
+ *
+ * - type: {@link ResultSet#TYPE_FORWARD_ONLY}
+ *
- concurrency: {@link ResultSet#CONCUR_READ_ONLY})
+ *
- holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
+ *
+ *
+ * @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");
+ }
}
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/Codes.java b/bindings/java/src/main/java/org/github/tursodatabase/core/Codes.java
new file mode 100644
index 000000000..0f8a3c402
--- /dev/null
+++ b/bindings/java/src/main/java/org/github/tursodatabase/core/Codes.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2007 David Crawshaw
+ *
+ * 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;
+}
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java b/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java
index f71827d07..98dd89ab3 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java
@@ -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
+ }
}
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java
index 9e67ae501..5883f7487 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java
@@ -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 {
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java
new file mode 100644
index 000000000..f1fb14221
--- /dev/null
+++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java
@@ -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 unwrap(Class iface) throws SQLException {
+ // TODO
+ return null;
+ }
+
+ @Override
+ public boolean isWrapperFor(Class> iface) throws SQLException {
+ // TODO
+ return false;
+ }
+}
diff --git a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java
new file mode 100644
index 000000000..bf2a20b88
--- /dev/null
+++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java
@@ -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);
+ });
+ }
+}