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 033a297c7..ebcbb8860 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,9 +1,9 @@ package org.github.tursodatabase.core; -import org.github.tursodatabase.annotations.Nullable; - import java.sql.SQLException; +import org.github.tursodatabase.annotations.Nullable; + /** * A table of data representing limbo database result set, which is generated by executing a statement that queries the * database. @@ -26,7 +26,7 @@ public class LimboResultSet { private boolean pastLastRow = false; @Nullable - private LimboStepResult lastResult; + private LimboStepResult lastStepResult; public static LimboResultSet of(LimboStatement statement) { return new LimboResultSet(statement); @@ -54,11 +54,18 @@ public class LimboResultSet { return false; } - lastResult = this.statement.step(); - pastLastRow = lastResult == null || lastResult.isDone(); + lastStepResult = this.statement.step(); + pastLastRow = lastStepResult == null || lastStepResult.isDone(); return !pastLastRow; } + /** + * Checks whether the last step result has returned row result. + */ + public boolean hasLastStepReturnedRow() { + return lastStepResult != null && lastStepResult.isRow(); + } + /** * Checks the status of the result set. * @@ -76,4 +83,17 @@ public class LimboResultSet { throw new SQLException("ResultSet closed"); } } + + @Override + public String toString() { + return "LimboResultSet{" + + "statement=" + statement + + ", isEmptyResultSet=" + isEmptyResultSet + + ", open=" + open + + ", maxRows=" + maxRows + + ", row=" + row + + ", pastLastRow=" + pastLastRow + + ", lastResult=" + lastStepResult + + '}'; + } } 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 0d2c2a169..1cad05d14 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 @@ -1,11 +1,11 @@ package org.github.tursodatabase.core; +import java.sql.SQLException; + import org.github.tursodatabase.annotations.NativeInvocation; import org.github.tursodatabase.annotations.Nullable; import org.github.tursodatabase.utils.LimboExceptionUtils; -import java.sql.SQLException; - /** * By default, only one resultSet object per LimboStatement can be open at the same time. * Therefore, if the reading of one resultSet object is interleaved with the reading of another, each must @@ -13,14 +13,13 @@ import java.sql.SQLException; * implicitly close the current resultSet object of the statement if an open one exists. */ public class LimboStatement { - + private final String sql; private final long statementPointer; private final LimboResultSet resultSet; - @Nullable - protected String sql = null; - - public LimboStatement(long statementPointer) { + // 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) { + this.sql = sql; this.statementPointer = statementPointer; this.resultSet = LimboResultSet.of(this); } @@ -29,12 +28,18 @@ public class LimboStatement { return resultSet; } - public void execute() throws SQLException { + /** + * Expects a clean statement created right after prepare method is called. + * + * @return true if the ResultSet has at least one row; false otherwise. + */ + public boolean execute() throws SQLException { resultSet.next(); + return resultSet.hasLastStepReturnedRow(); } @Nullable - public LimboStepResult step() throws SQLException { + LimboStepResult step() throws SQLException { return step(this.statementPointer); } @@ -44,11 +49,19 @@ public class LimboStatement { /** * Throws formatted SQLException with error code and message. * - * @param errorCode Error code. + * @param errorCode Error code. * @param errorMessageBytes Error message. */ - @NativeInvocation + @NativeInvocation(invokedFrom = "limbo_statement.rs") private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException { LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes); } + + @Override + public String toString() { + return "LimboStatement{" + + "statementPointer=" + statementPointer + + ", sql='" + sql + '\'' + + '}'; + } } diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStepResult.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStepResult.java index de501da29..71e343d90 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStepResult.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStepResult.java @@ -8,23 +8,27 @@ import org.github.tursodatabase.annotations.NativeInvocation; * Represents the step result of limbo's statement's step function. */ public class LimboStepResult { - public static final int STEP_RESULT_ID_ROW = 10; - public static final int STEP_RESULT_ID_IO = 20; - public static final int STEP_RESULT_ID_DONE = 30; - public static final int STEP_RESULT_ID_INTERRUPT = 40; - public static final int STEP_RESULT_ID_BUSY = 50; - public static final int STEP_RESULT_ID_ERROR = 60; + private static final int STEP_RESULT_ID_ROW = 10; + private static final int STEP_RESULT_ID_IO = 20; + private static final int STEP_RESULT_ID_DONE = 30; + private static final int STEP_RESULT_ID_INTERRUPT = 40; + private static final int STEP_RESULT_ID_BUSY = 50; + private static final int STEP_RESULT_ID_ERROR = 60; // Identifier for limbo's StepResult private final int stepResultId; private final Object[] result; - @NativeInvocation + @NativeInvocation(invokedFrom = "limbo_statement.rs") public LimboStepResult(int stepResultId, Object[] result) { this.stepResultId = stepResultId; this.result = result; } + public boolean isRow() { + return stepResultId == STEP_RESULT_ID_ROW; + } + public boolean isDone() { return stepResultId == STEP_RESULT_ID_DONE; } 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 03f3864a7..2f7f2c0d6 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 @@ -135,10 +135,6 @@ public class JDBC4Statement implements Statement { * getResultSet or getUpdateCount * to retrieve the result, and getMoreResults to * move to any subsequent result(s). - * - * @return true if the first result is a ResultSet - * object; false if it is an update count or there are - * no results */ @Override public boolean execute(String sql) throws SQLException { @@ -147,13 +143,14 @@ public class JDBC4Statement implements Statement { return this.withConnectionTimeout( () -> { try { + // TODO: if sql is a readOnly query, do we still need the locks? connectionLock.lock(); statement = connection.prepare(sql); - statement.execute(); + final boolean result = statement.execute(); updateGeneratedKeys(); exhaustedResults = false; - // TODO: determine whether - return true; + + return result; } finally { connectionLock.unlock(); } 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 new file mode 100644 index 000000000..f81e9d482 --- /dev/null +++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4StatementTest.java @@ -0,0 +1,53 @@ +package org.github.tursodatabase.jdbc4; + +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Properties; + +import org.github.tursodatabase.TestUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class JDBC4StatementTest { + + private Statement stmt; + + @BeforeEach + void setUp() throws Exception { + String filePath = TestUtils.createTempFile(); + String url = "jdbc:sqlite:" + filePath; + final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties()); + stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + } + + @Test + void execute_ddl_should_return_false() throws Exception{ + assertFalse(stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);")); + } + + @Test + void execute_insert_should_return_false() throws Exception { + stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + assertFalse(stmt.execute("INSERT INTO users VALUES (1, 'limbo');")); + } + + @Test + @Disabled("UPDATE not supported yet") + void execute_update_should_return_false() throws Exception { + stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("INSERT INTO users VALUES (1, 'limbo');"); + assertFalse(stmt.execute("UPDATE users SET username = 'seonwoo' WHERE id = 1;")); + } + + @Test + void execute_select_should_return_true() throws Exception { + stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("INSERT INTO users VALUES (1, 'limbo');"); + assertTrue(stmt.execute("SELECT * FROM users;")); + } +}