diff --git a/bindings/java/rs_src/limbo_statement.rs b/bindings/java/rs_src/limbo_statement.rs
index 7de4b2c19..aed8e7d99 100644
--- a/bindings/java/rs_src/limbo_statement.rs
+++ b/bindings/java/rs_src/limbo_statement.rs
@@ -29,7 +29,6 @@ impl LimboStatement {
Box::into_raw(Box::new(self)) as jlong
}
- #[allow(dead_code)]
pub fn drop(ptr: jlong) {
let _boxed = unsafe { Box::from_raw(ptr as *mut LimboStatement) };
}
@@ -88,6 +87,15 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboStatement_step<'l
}
}
+#[no_mangle]
+pub extern "system" fn Java_org_github_tursodatabase_core_LimboStatement__1close<'local>(
+ _env: JNIEnv<'local>,
+ _obj: JObject<'local>,
+ stmt_ptr: jlong,
+) {
+ LimboStatement::drop(stmt_ptr);
+}
+
fn row_to_obj_array<'local>(
env: &mut JNIEnv<'local>,
row: &limbo_core::Row,
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboResultSet.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboResultSet.java
index c6cb8d00e..b226c53d0 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboResultSet.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboResultSet.java
@@ -1,5 +1,6 @@
package org.github.tursodatabase.core;
+import java.sql.ResultSet;
import java.sql.SQLException;
import org.github.tursodatabase.annotations.Nullable;
import org.slf4j.Logger;
@@ -39,6 +40,20 @@ public class LimboResultSet {
this.statement = statement;
}
+ /**
+ * Consumes all the rows in this {@link ResultSet} until the {@link #next()} method returns
+ * `false`.
+ *
+ * @throws SQLException if the result set is not open or if an error occurs while iterating.
+ */
+ public void consumeAll() throws SQLException {
+ if (!open) {
+ throw new SQLException("The result set is not open");
+ }
+
+ while (next()) {}
+ }
+
/**
* Moves the cursor forward one row from its current position. A {@link LimboResultSet} cursor is
* initially positioned before the first fow; the first call to the method next makes
@@ -50,7 +65,11 @@ public class LimboResultSet {
* cursor can only move forward.
*/
public boolean next() throws SQLException {
- if (!open || isEmptyResultSet || pastLastRow) {
+ if (!open) {
+ throw new SQLException("The resultSet is not open");
+ }
+
+ if (isEmptyResultSet || pastLastRow) {
return false; // completed ResultSet
}
@@ -70,9 +89,6 @@ public class LimboResultSet {
}
pastLastRow = lastStepResult.isDone();
- if (pastLastRow) {
- open = false;
- }
return !pastLastRow;
}
@@ -97,6 +113,10 @@ public class LimboResultSet {
}
}
+ public void close() throws SQLException {
+ this.open = false;
+ }
+
@Override
public String toString() {
return "LimboResultSet{"
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStatement.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStatement.java
index c749e27cc..8566c403e 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStatement.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStatement.java
@@ -21,6 +21,8 @@ public class LimboStatement {
private final long statementPointer;
private final LimboResultSet resultSet;
+ private boolean closed;
+
// TODO: what if the statement we ran was DDL, update queries and etc. Should we still create a
// resultSet?
public LimboStatement(String sql, long statementPointer) {
@@ -67,6 +69,30 @@ public class LimboStatement {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
+ /**
+ * Closes the current statement and releases any resources associated with it. This method calls
+ * the native `_close` method to perform the actual closing operation.
+ */
+ public void close() throws SQLException {
+ if (closed) {
+ return;
+ }
+ this.resultSet.close();
+ _close(statementPointer);
+ closed = true;
+ }
+
+ private native void _close(long statementPointer);
+
+ /**
+ * Checks if the statement is closed.
+ *
+ * @return true if the statement is closed, false otherwise.
+ */
+ public boolean isClosed() {
+ return closed;
+ }
+
@Override
public String toString() {
return "LimboStatement{"
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4ResultSet.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4ResultSet.java
index 867b2688e..092bf4d44 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4ResultSet.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4ResultSet.java
@@ -25,7 +25,7 @@ public class JDBC4ResultSet implements ResultSet {
@Override
public void close() throws SQLException {
- // TODO
+ resultSet.close();
}
@Override
@@ -866,8 +866,7 @@ public class JDBC4ResultSet implements ResultSet {
@Override
public boolean isClosed() throws SQLException {
- // TODO
- return false;
+ return !resultSet.isOpen();
}
@Override
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
index eee4c95a3..7cbf6f69d 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java
@@ -19,6 +19,8 @@ public class JDBC4Statement implements Statement {
private final LimboConnection connection;
@Nullable private LimboStatement statement = null;
+ // Because JDBC4Statement has different life cycle in compared to LimboStatement, let's use this
+ // field to manage JDBC4Statement lifecycle
private boolean closed;
private boolean closeOnCompletion;
@@ -65,9 +67,7 @@ public class JDBC4Statement implements Statement {
requireNonNull(statement, "statement should not be null after running execute method");
final LimboResultSet resultSet = statement.getResultSet();
- while (resultSet.isOpen()) {
- resultSet.next();
- }
+ resultSet.consumeAll();
// TODO: return update count;
return 0;
@@ -75,8 +75,14 @@ public class JDBC4Statement implements Statement {
@Override
public void close() throws SQLException {
- clearGeneratedKeys();
- internalClose();
+ if (closed) {
+ return;
+ }
+
+ if (this.statement != null) {
+ this.statement.close();
+ }
+
closed = true;
}
@@ -150,8 +156,7 @@ public class JDBC4Statement implements Statement {
*/
@Override
public boolean execute(String sql) throws SQLException {
- internalClose();
-
+ ensureOpen();
return this.withConnectionTimeout(
() -> {
try {
@@ -298,8 +303,7 @@ public class JDBC4Statement implements Statement {
@Override
public boolean isClosed() throws SQLException {
- // TODO
- return false;
+ return this.closed;
}
@Override
@@ -346,14 +350,6 @@ public class JDBC4Statement implements Statement {
return false;
}
- protected void internalClose() throws SQLException {
- // TODO
- }
-
- protected void clearGeneratedKeys() throws SQLException {
- // TODO
- }
-
protected void updateGeneratedKeys() throws SQLException {
// TODO
}
@@ -378,4 +374,10 @@ public class JDBC4Statement implements Statement {
protected interface SQLCallable {
T call() throws SQLException;
}
+
+ private void ensureOpen() throws SQLException {
+ if (closed) {
+ throw new SQLException("Statement is closed");
+ }
+ }
}
diff --git a/bindings/java/src/test/java/org/github/tursodatabase/core/LimboStatementTest.java b/bindings/java/src/test/java/org/github/tursodatabase/core/LimboStatementTest.java
new file mode 100644
index 000000000..fe274b07e
--- /dev/null
+++ b/bindings/java/src/test/java/org/github/tursodatabase/core/LimboStatementTest.java
@@ -0,0 +1,31 @@
+package org.github.tursodatabase.core;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Properties;
+import org.github.tursodatabase.TestUtils;
+import org.github.tursodatabase.jdbc4.JDBC4Connection;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class LimboStatementTest {
+
+ private JDBC4Connection connection;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ String filePath = TestUtils.createTempFile();
+ String url = "jdbc:sqlite:" + filePath;
+ connection = new JDBC4Connection(url, filePath, new Properties());
+ }
+
+ @Test
+ void closing_statement_closes_related_resources() throws Exception {
+ LimboStatement stmt = connection.prepare("SELECT 1;");
+ stmt.execute();
+
+ stmt.close();
+ assertTrue(stmt.isClosed());
+ assertFalse(stmt.getResultSet().isOpen());
+ }
+}
diff --git a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ResultSetTest.java b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ResultSetTest.java
index f764a9361..a16c096c9 100644
--- a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ResultSetTest.java
+++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ResultSetTest.java
@@ -1,9 +1,11 @@
package org.github.tursodatabase.jdbc4;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.sql.ResultSet;
+import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.github.tursodatabase.TestUtils;
@@ -57,4 +59,24 @@ class JDBC4ResultSetTest {
// as well
assertFalse(resultSet.next());
}
+
+ @Test
+ void close_resultSet_test() throws Exception {
+ stmt.executeQuery("SELECT 1;");
+ ResultSet resultSet = stmt.getResultSet();
+
+ assertFalse(resultSet.isClosed());
+ resultSet.close();
+ assertTrue(resultSet.isClosed());
+ }
+
+ @Test
+ void calling_methods_on_closed_resultSet_should_throw_exception() throws Exception {
+ stmt.executeQuery("SELECT 1;");
+ ResultSet resultSet = stmt.getResultSet();
+ resultSet.close();
+ assertTrue(resultSet.isClosed());
+
+ assertThrows(SQLException.class, resultSet::next);
+ }
}
diff --git a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4StatementTest.java b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4StatementTest.java
index 2a837629d..a48cedea9 100644
--- a/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4StatementTest.java
+++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4StatementTest.java
@@ -3,6 +3,7 @@ package org.github.tursodatabase.jdbc4;
import static org.junit.jupiter.api.Assertions.*;
import java.sql.ResultSet;
+import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.github.tursodatabase.TestUtils;
@@ -51,4 +52,22 @@ class JDBC4StatementTest {
stmt.execute("INSERT INTO users VALUES (1, 'limbo');");
assertTrue(stmt.execute("SELECT * FROM users;"));
}
+
+ @Test
+ void close_statement_test() throws Exception {
+ stmt.close();
+ assertTrue(stmt.isClosed());
+ }
+
+ @Test
+ void double_close_is_no_op() throws SQLException {
+ stmt.close();
+ assertDoesNotThrow(() -> stmt.close());
+ }
+
+ @Test
+ void operations_on_closed_statement_should_throw_exception() throws Exception {
+ stmt.close();
+ assertThrows(SQLException.class, () -> stmt.execute("SELECT 1;"));
+ }
}