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 fd2845a17..033a297c7 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,7 @@ package org.github.tursodatabase.core; +import org.github.tursodatabase.annotations.Nullable; + import java.sql.SQLException; /** @@ -21,9 +23,11 @@ public class LimboResultSet { private long maxRows; // number of current row, starts at 1 (0 is used to represent loading data) private int row = 0; - private boolean pastLastRow = false; + @Nullable + private LimboStepResult lastResult; + public static LimboResultSet of(LimboStatement statement) { return new LimboResultSet(statement); } @@ -33,6 +37,14 @@ public class LimboResultSet { this.statement = statement; } + /** + * 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 the first row the current row; the second call + * makes the second row the current row, and so on. + * When a call to the next method returns false, the cursor is positioned after the last row. + *

+ * Note that limbo only supports ResultSet.TYPE_FORWARD_ONLY, which means that the cursor can only move forward. + */ public boolean next() throws SQLException { if (!open || isEmptyResultSet || pastLastRow) { return false; // completed ResultSet @@ -42,10 +54,9 @@ public class LimboResultSet { return false; } - // TODO - // int statusCode = this.statement.step(); - this.statement.step(); - return true; + lastResult = this.statement.step(); + pastLastRow = lastResult == null || lastResult.isDone(); + return !pastLastRow; } /** 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 8dc6e3346..0d2c2a169 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 @@ -25,21 +25,20 @@ public class LimboStatement { this.resultSet = LimboResultSet.of(this); } - public LimboResultSet resultSet() { + public LimboResultSet getResultSet() { return resultSet; } public void execute() throws SQLException { - LimboResultSet result = LimboResultSet.of(this); - - // at least, run query minimally - result.next(); + resultSet.next(); } + @Nullable public LimboStepResult step() throws SQLException { return step(this.statementPointer); } + @Nullable private native LimboStepResult step(long stmtPointer) throws SQLException; /** 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 5a33920b7..de501da29 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 @@ -1,5 +1,7 @@ package org.github.tursodatabase.core; +import java.util.Arrays; + import org.github.tursodatabase.annotations.NativeInvocation; /** @@ -22,4 +24,16 @@ public class LimboStepResult { this.stepResultId = stepResultId; this.result = result; } + + public boolean isDone() { + return stepResultId == STEP_RESULT_ID_DONE; + } + + @Override + public String toString() { + return "LimboStepResult{" + + "stepResultId=" + stepResultId + + ", result=" + Arrays.toString(result) + + '}'; + } } 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 69b51a8f0..0d7cce084 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 @@ -21,8 +21,7 @@ public class JDBC4ResultSet implements ResultSet { @Override public boolean next() throws SQLException { - // TODO - return false; + return resultSet.next(); } @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 31726a5d9..03f3864a7 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 @@ -1,13 +1,19 @@ package org.github.tursodatabase.jdbc4; +import static java.util.Objects.requireNonNull; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.concurrent.locks.ReentrantLock; + import org.github.tursodatabase.annotations.Nullable; import org.github.tursodatabase.annotations.SkipNullableCheck; import org.github.tursodatabase.core.LimboConnection; import org.github.tursodatabase.core.LimboStatement; -import java.sql.*; -import java.util.concurrent.locks.ReentrantLock; - public class JDBC4Statement implements Statement { private final LimboConnection connection; @@ -28,10 +34,12 @@ public class JDBC4Statement implements Statement { private ReentrantLock connectionLock = new ReentrantLock(); public JDBC4Statement(LimboConnection connection) { - this(connection, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); + 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) { + public JDBC4Statement(LimboConnection connection, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) { this.connection = connection; this.resultSetType = resultSetType; this.resultSetConcurrency = resultSetConcurrency; @@ -121,6 +129,17 @@ public class JDBC4Statement implements Statement { // TODO } + /** + * The execute method executes an SQL statement and indicates the + * form of the first result. You must then use the methods + * 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 { internalClose(); @@ -133,8 +152,8 @@ public class JDBC4Statement implements Statement { statement.execute(); updateGeneratedKeys(); exhaustedResults = false; + // TODO: determine whether return true; - // return !result.isEmpty(); } finally { connectionLock.unlock(); } @@ -143,10 +162,9 @@ public class JDBC4Statement implements Statement { } @Override - @SkipNullableCheck public ResultSet getResultSet() throws SQLException { - // TODO - return null; + requireNonNull(statement, "statement is null"); + return new JDBC4ResultSet(statement.getResultSet()); } @Override @@ -289,7 +307,7 @@ public class JDBC4Statement implements Statement { @Override public void closeOnCompletion() throws SQLException { - if (closed) throw new SQLException("statement is closed"); + if (closed) {throw new SQLException("statement is closed");} closeOnCompletion = true; } @@ -298,7 +316,7 @@ public class JDBC4Statement implements Statement { */ @Override public boolean isCloseOnCompletion() throws SQLException { - if (closed) throw new SQLException("statement is closed"); + if (closed) {throw new SQLException("statement is closed");} return closeOnCompletion; } diff --git a/bindings/java/src/main/java/org/github/tursodatabase/utils/LimboExceptionUtils.java b/bindings/java/src/main/java/org/github/tursodatabase/utils/LimboExceptionUtils.java index 9a45db040..08e8f5b5e 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/utils/LimboExceptionUtils.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/utils/LimboExceptionUtils.java @@ -12,7 +12,7 @@ public class LimboExceptionUtils { /** * Throws formatted SQLException with error code and message. * - * @param errorCode Error code. + * @param errorCode Error code. * @param errorMessageBytes Error message. */ public static void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException { @@ -23,10 +23,11 @@ public class LimboExceptionUtils { /** * Throws formatted SQLException with error code and message. * - * @param errorCode Error code. + * @param errorCode Error code. * @param errorMessage Error message. */ - public static LimboException buildLimboException(int errorCode, @Nullable String errorMessage) throws SQLException { + public static LimboException buildLimboException(int errorCode, @Nullable String errorMessage) + throws SQLException { LimboErrorCode code = LimboErrorCode.getErrorCode(errorCode); String msg; if (code == LimboErrorCode.UNKNOWN_ERROR) { @@ -37,4 +38,18 @@ public class LimboExceptionUtils { return new LimboException(msg, code); } + + /** + * Ensures that the provided object is not null. + * + * @param object the object to check for nullity + * @param message the message to include in the exception if the object is null + * + * @throws IllegalArgumentException if the provided object is null + */ + public static void requireNonNull(Object object, String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + } } diff --git a/bindings/java/src/test/java/org/github/tursodatabase/IntegrationTest.java b/bindings/java/src/test/java/org/github/tursodatabase/IntegrationTest.java index 873c41476..be25ffdff 100644 --- a/bindings/java/src/test/java/org/github/tursodatabase/IntegrationTest.java +++ b/bindings/java/src/test/java/org/github/tursodatabase/IntegrationTest.java @@ -22,7 +22,6 @@ public class IntegrationTest { } @Test - @Disabled("Doesn't work on workflow. Need investigation.") void create_table_multi_inserts_select() throws Exception { Statement stmt = createDefaultStatement(); stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); 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 new file mode 100644 index 000000000..d32ed2731 --- /dev/null +++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ResultSetTest.java @@ -0,0 +1,58 @@ +package org.github.tursodatabase.jdbc4; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.Test; + +class JDBC4ResultSetTest { + + 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 invoking_next_before_the_last_row_should_return_true() throws Exception { + stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("INSERT INTO users VALUES (1, 'sinwoo');"); + stmt.execute("INSERT INTO users VALUES (2, 'seonwoo');"); + + // first call to next occur internally + stmt.execute("SELECT * FROM users"); + ResultSet resultSet = stmt.getResultSet(); + + assertTrue(resultSet.next()); + } + + @Test + void invoking_next_after_the_last_row_should_return_false() throws Exception { + stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"); + stmt.execute("INSERT INTO users VALUES (1, 'sinwoo');"); + stmt.execute("INSERT INTO users VALUES (2, 'seonwoo');"); + + // first call to next occur internally + stmt.execute("SELECT * FROM users"); + ResultSet resultSet = stmt.getResultSet(); + + while (resultSet.next()) { + // this loop will break when resultSet returns false + } + + // if the previous call to next() returned false, consecutive call to next() should return false as well + assertFalse(resultSet.next()); + } +}