mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-05 09:14:24 +01:00
Merge 'bindings/java: Implement JDBC ResultSet' from Kim Seon Woo
## Purpose of this PR Associate jdbc's `ResultSet` with the returned values from limbo's step function. ## Changes ### Rust - `Java_org_github_tursodatabase_core_LimboStatement_step` now returns an object of java's `LimboStepResult.java` ### Java - Added `LimboStepResult.java` in order to distinguish the type of `StepResult`(which limbo returns) and to encapsulate the interpretation of limbo's `StepResult` - Change `JDBC4ResultSet` inheriting `LimboResultSet` to composition. IMO when using inheritance, it's too burdensome to fit unmatching parts together. - Enhance `JDBC4Statement.java`'s `execute` method - By looking at the `ResultSet` created after executing the qury, it's now able to determine the (boolean) result. ## Reference - https://github.com/tursodatabase/limbo/issues/615 Closes #743
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
.PHONY: test build_test
|
||||
|
||||
test: build_test
|
||||
./gradlew test
|
||||
./gradlew test --info
|
||||
|
||||
build_test:
|
||||
CARGO_TARGET_DIR=src/test/resources/limbo cargo build
|
||||
|
||||
@@ -6,6 +6,13 @@ use jni::sys::jlong;
|
||||
use jni::JNIEnv;
|
||||
use limbo_core::{Statement, StepResult};
|
||||
|
||||
pub const STEP_RESULT_ID_ROW: i32 = 10;
|
||||
pub const STEP_RESULT_ID_IO: i32 = 20;
|
||||
pub const STEP_RESULT_ID_DONE: i32 = 30;
|
||||
pub const STEP_RESULT_ID_INTERRUPT: i32 = 40;
|
||||
pub const STEP_RESULT_ID_BUSY: i32 = 50;
|
||||
pub const STEP_RESULT_ID_ERROR: i32 = 60;
|
||||
|
||||
pub struct LimboStatement {
|
||||
pub(crate) stmt: Statement,
|
||||
}
|
||||
@@ -50,26 +57,26 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboStatement_step<'l
|
||||
|
||||
match stmt.stmt.step() {
|
||||
Ok(StepResult::Row(row)) => match row_to_obj_array(&mut env, &row) {
|
||||
Ok(row) => row,
|
||||
Ok(row) => to_limbo_step_result(&mut env, STEP_RESULT_ID_ROW, Some(row)),
|
||||
Err(e) => {
|
||||
set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
|
||||
|
||||
JObject::null()
|
||||
to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None)
|
||||
}
|
||||
},
|
||||
Ok(StepResult::IO) => match env.new_object_array(0, "java/lang/Object", JObject::null()) {
|
||||
Ok(row) => row.into(),
|
||||
Ok(row) => to_limbo_step_result(&mut env, STEP_RESULT_ID_IO, Some(row.into())),
|
||||
Err(e) => {
|
||||
set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
|
||||
|
||||
JObject::null()
|
||||
to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None)
|
||||
}
|
||||
},
|
||||
_ => JObject::null(),
|
||||
Ok(StepResult::Done) => to_limbo_step_result(&mut env, STEP_RESULT_ID_DONE, None),
|
||||
Ok(StepResult::Interrupt) => to_limbo_step_result(&mut env, STEP_RESULT_ID_INTERRUPT, None),
|
||||
Ok(StepResult::Busy) => to_limbo_step_result(&mut env, STEP_RESULT_ID_BUSY, None),
|
||||
_ => to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn row_to_obj_array<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
row: &limbo_core::Row,
|
||||
@@ -96,3 +103,42 @@ fn row_to_obj_array<'local>(
|
||||
|
||||
Ok(obj_array.into())
|
||||
}
|
||||
|
||||
/// Converts an optional `JObject` into Java's `LimboStepResult`.
|
||||
///
|
||||
/// This function takes an optional `JObject` and converts it into a Java object
|
||||
/// of type `LimboStepResult`. The conversion is done by creating a new Java object with the
|
||||
/// appropriate constructor arguments.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `env` - A mutable reference to the JNI environment.
|
||||
/// * `id` - An integer representing the type of `StepResult`.
|
||||
/// * `result` - An optional `JObject` that contains the result data.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `JObject` representing the `LimboStepResult` in Java. If the object creation fails,
|
||||
/// a null `JObject` is returned
|
||||
fn to_limbo_step_result<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
id: i32,
|
||||
result: Option<JObject<'local>>,
|
||||
) -> JObject<'local> {
|
||||
let mut ctor_args = vec![JValue::Int(id)];
|
||||
if let Some(res) = result {
|
||||
ctor_args.push(JValue::Object(&res));
|
||||
env.new_object(
|
||||
"org/github/tursodatabase/core/LimboStepResult",
|
||||
"(I[Ljava/lang/Object;)V",
|
||||
&ctor_args,
|
||||
)
|
||||
} else {
|
||||
env.new_object(
|
||||
"org/github/tursodatabase/core/LimboStepResult",
|
||||
"(I)V",
|
||||
&ctor_args,
|
||||
)
|
||||
}
|
||||
.unwrap_or_else(|_| JObject::null())
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark methods that are called by native functions.
|
||||
* For example, throwing exceptions or creating java objects.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.METHOD)
|
||||
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
|
||||
public @interface NativeInvocation {
|
||||
String invokedFrom() default "";
|
||||
}
|
||||
|
||||
@@ -79,13 +79,13 @@ public abstract class LimboConnection implements Connection {
|
||||
* @return Pointer to statement.
|
||||
* @throws SQLException if a database access error occurs.
|
||||
*/
|
||||
public long prepare(String sql) throws SQLException {
|
||||
public LimboStatement prepare(String sql) throws SQLException {
|
||||
logger.trace("DriverManager [{}] [SQLite EXEC] {}", Thread.currentThread().getName(), sql);
|
||||
byte[] sqlBytes = stringToUtf8ByteArray(sql);
|
||||
if (sqlBytes == null) {
|
||||
throw new SQLException("Failed to convert " + sql + " into bytes");
|
||||
}
|
||||
return prepareUtf8(connectionPtr, sqlBytes);
|
||||
return new LimboStatement(sql, prepareUtf8(connectionPtr, sqlBytes));
|
||||
}
|
||||
|
||||
private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;
|
||||
@@ -133,7 +133,7 @@ public abstract class LimboConnection implements Connection {
|
||||
* @param errorCode Error code.
|
||||
* @param errorMessageBytes Error message.
|
||||
*/
|
||||
@NativeInvocation
|
||||
@NativeInvocation(invokedFrom = "limbo_connection.rs")
|
||||
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
|
||||
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public final class LimboDB extends AbstractDB {
|
||||
* @param errorCode Error code.
|
||||
* @param errorMessageBytes Error message.
|
||||
*/
|
||||
@NativeInvocation
|
||||
@NativeInvocation(invokedFrom = "limbo_db.rs")
|
||||
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
|
||||
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
|
||||
}
|
||||
|
||||
@@ -2,26 +2,82 @@ package org.github.tursodatabase.core;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* JDBC ResultSet.
|
||||
*/
|
||||
public abstract class LimboResultSet {
|
||||
import org.github.tursodatabase.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
protected final LimboStatement statement;
|
||||
/**
|
||||
* A table of data representing limbo database result set, which is generated by executing a statement that queries the
|
||||
* database.
|
||||
* <p>
|
||||
* A {@link LimboResultSet} object is automatically closed when the {@link LimboStatement} object that generated it is
|
||||
* closed or re-executed.
|
||||
*/
|
||||
public class LimboResultSet {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LimboResultSet.class);
|
||||
|
||||
private final LimboStatement statement;
|
||||
|
||||
// Whether the result set does not have any rows.
|
||||
protected boolean isEmptyResultSet = false;
|
||||
private boolean isEmptyResultSet = false;
|
||||
// If the result set is open. Doesn't mean it has results.
|
||||
private boolean open = false;
|
||||
private boolean open;
|
||||
// Maximum number of rows as set by the statement
|
||||
protected long maxRows;
|
||||
private long maxRows;
|
||||
// number of current row, starts at 1 (0 is used to represent loading data)
|
||||
protected int row = 0;
|
||||
private int row = 0;
|
||||
private boolean pastLastRow = false;
|
||||
|
||||
protected LimboResultSet(LimboStatement statement) {
|
||||
@Nullable
|
||||
private LimboStepResult lastStepResult;
|
||||
|
||||
public static LimboResultSet of(LimboStatement statement) {
|
||||
return new LimboResultSet(statement);
|
||||
}
|
||||
|
||||
private LimboResultSet(LimboStatement statement) {
|
||||
this.open = true;
|
||||
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 <code>next</code> 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 <code>next</code> method returns <code>false</code>, the cursor is positioned after the last row.
|
||||
* <p>
|
||||
* Note that limbo only supports <code>ResultSet.TYPE_FORWARD_ONLY</code>, which means that the cursor can only move forward.
|
||||
*/
|
||||
public boolean next() throws SQLException {
|
||||
if (!open || isEmptyResultSet || pastLastRow) {
|
||||
return false; // completed ResultSet
|
||||
}
|
||||
|
||||
if (maxRows != 0 && row == maxRows) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastStepResult = this.statement.step();
|
||||
log.debug("lastStepResult: {}", lastStepResult);
|
||||
if (lastStepResult.isRow()) {
|
||||
row++;
|
||||
}
|
||||
|
||||
pastLastRow = lastStepResult.isDone();
|
||||
if (pastLastRow) {
|
||||
open = false;
|
||||
}
|
||||
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.
|
||||
*
|
||||
@@ -34,9 +90,22 @@ public abstract class LimboResultSet {
|
||||
/**
|
||||
* @throws SQLException if not {@link #open}
|
||||
*/
|
||||
protected void checkOpen() throws SQLException {
|
||||
public void checkOpen() throws SQLException {
|
||||
if (!open) {
|
||||
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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,76 @@
|
||||
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.jdbc4.JDBC4ResultSet;
|
||||
import org.github.tursodatabase.utils.LimboExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
/**
|
||||
* By default, only one <code>resultSet</code> object per <code>LimboStatement</code> can be open at the same time.
|
||||
* Therefore, if the reading of one <code>resultSet</code> object is interleaved with the reading of another, each must
|
||||
* have been generated by different <code>LimboStatement</code> objects. All execution method in the <code>LimboStatement</code>
|
||||
* implicitly close the current <code>resultSet</code> object of the statement if an open one exists.
|
||||
*/
|
||||
public class LimboStatement {
|
||||
private static final Logger log = LoggerFactory.getLogger(LimboStatement.class);
|
||||
|
||||
public abstract class LimboStatement {
|
||||
private final String sql;
|
||||
private final long statementPointer;
|
||||
private final LimboResultSet resultSet;
|
||||
|
||||
protected final LimboConnection connection;
|
||||
protected final LimboResultSet resultSet;
|
||||
|
||||
@Nullable
|
||||
protected String sql = null;
|
||||
|
||||
protected LimboStatement(LimboConnection connection) {
|
||||
this.connection = connection;
|
||||
this.resultSet = new JDBC4ResultSet(this);
|
||||
// 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);
|
||||
log.debug("Creating statement with sql: {}", this.sql);
|
||||
}
|
||||
|
||||
protected void internalClose() throws SQLException {
|
||||
// TODO
|
||||
public LimboResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
protected void clearGeneratedKeys() throws SQLException {
|
||||
// TODO
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
protected void updateGeneratedKeys() throws SQLException {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// TODO: associate the result with CoreResultSet
|
||||
// TODO: we can make this async!!
|
||||
// TODO: distinguish queries that return result or doesn't return result
|
||||
protected List<Object[]> execute(long stmtPointer) throws SQLException {
|
||||
List<Object[]> result = new ArrayList<>();
|
||||
while (true) {
|
||||
Object[] stepResult = step(stmtPointer);
|
||||
if (stepResult != null) {
|
||||
for (int i = 0; i < stepResult.length; i++) {
|
||||
System.out.println("stepResult" + i + ": " + stepResult[i]);
|
||||
}
|
||||
}
|
||||
if (stepResult == null) break;
|
||||
result.add(stepResult);
|
||||
LimboStepResult step() throws SQLException {
|
||||
final LimboStepResult result = step(this.statementPointer);
|
||||
if (result == null) {
|
||||
throw new SQLException("step() returned null, which is only returned when an error occurs");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private native Object[] step(long stmtPointer) throws SQLException;
|
||||
@Nullable
|
||||
private native LimboStepResult step(long stmtPointer) throws SQLException;
|
||||
|
||||
/**
|
||||
* 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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.github.tursodatabase.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.github.tursodatabase.annotations.NativeInvocation;
|
||||
import org.github.tursodatabase.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the step result of limbo's statement's step function.
|
||||
*/
|
||||
public class LimboStepResult {
|
||||
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;
|
||||
@Nullable
|
||||
private final Object[] result;
|
||||
|
||||
@NativeInvocation(invokedFrom = "limbo_statement.rs")
|
||||
public LimboStepResult(int stepResultId) {
|
||||
this.stepResultId = stepResultId;
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LimboStepResult{" +
|
||||
"stepResultName=" + getStepResultName() +
|
||||
", result=" + Arrays.toString(result) +
|
||||
'}';
|
||||
}
|
||||
|
||||
private String getStepResultName() {
|
||||
switch (stepResultId) {
|
||||
case STEP_RESULT_ID_ROW:
|
||||
return "ROW";
|
||||
case STEP_RESULT_ID_IO:
|
||||
return "IO";
|
||||
case STEP_RESULT_ID_DONE:
|
||||
return "DONE";
|
||||
case STEP_RESULT_ID_INTERRUPT:
|
||||
return "INTERRUPT";
|
||||
case STEP_RESULT_ID_BUSY:
|
||||
return "BUSY";
|
||||
case STEP_RESULT_ID_ERROR:
|
||||
return "ERROR";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.github.tursodatabase.jdbc4;
|
||||
|
||||
import org.github.tursodatabase.annotations.SkipNullableCheck;
|
||||
import org.github.tursodatabase.core.LimboResultSet;
|
||||
import org.github.tursodatabase.core.LimboStatement;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
@@ -12,16 +11,17 @@ import java.sql.*;
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
|
||||
public class JDBC4ResultSet extends LimboResultSet implements ResultSet {
|
||||
public class JDBC4ResultSet implements ResultSet {
|
||||
|
||||
public JDBC4ResultSet(LimboStatement statement) {
|
||||
super(statement);
|
||||
private final LimboResultSet resultSet;
|
||||
|
||||
public JDBC4ResultSet(LimboResultSet resultSet) {
|
||||
this.resultSet = resultSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean next() throws SQLException {
|
||||
// TODO
|
||||
return false;
|
||||
return resultSet.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
package org.github.tursodatabase.jdbc4;
|
||||
|
||||
import org.github.tursodatabase.annotations.SkipNullableCheck;
|
||||
import org.github.tursodatabase.core.LimboConnection;
|
||||
import org.github.tursodatabase.core.LimboStatement;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link Statement} interface for JDBC 4.
|
||||
*/
|
||||
public class JDBC4Statement extends LimboStatement implements Statement {
|
||||
import org.github.tursodatabase.annotations.Nullable;
|
||||
import org.github.tursodatabase.annotations.SkipNullableCheck;
|
||||
import org.github.tursodatabase.core.LimboConnection;
|
||||
import org.github.tursodatabase.core.LimboResultSet;
|
||||
import org.github.tursodatabase.core.LimboStatement;
|
||||
|
||||
public class JDBC4Statement implements Statement {
|
||||
|
||||
private final LimboConnection connection;
|
||||
@Nullable
|
||||
private LimboStatement statement = null;
|
||||
|
||||
private boolean closed;
|
||||
private boolean closeOnCompletion;
|
||||
@@ -28,26 +35,37 @@ public class JDBC4Statement extends LimboStatement 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) {
|
||||
super(connection);
|
||||
public JDBC4Statement(LimboConnection connection, int resultSetType, int resultSetConcurrency,
|
||||
int resultSetHoldability) {
|
||||
this.connection = connection;
|
||||
this.resultSetType = resultSetType;
|
||||
this.resultSetConcurrency = resultSetConcurrency;
|
||||
this.resultSetHoldability = resultSetHoldability;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SkipNullableCheck
|
||||
public ResultSet executeQuery(String sql) throws SQLException {
|
||||
// TODO
|
||||
return null;
|
||||
execute(sql);
|
||||
|
||||
requireNonNull(statement, "statement should not be null after running execute method");
|
||||
return new JDBC4ResultSet(statement.getResultSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate(String sql) throws SQLException {
|
||||
// TODO
|
||||
execute(sql);
|
||||
|
||||
requireNonNull(statement, "statement should not be null after running execute method");
|
||||
final LimboResultSet resultSet = statement.getResultSet();
|
||||
while (resultSet.isOpen()) {
|
||||
resultSet.next();
|
||||
}
|
||||
|
||||
// TODO: return update count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -121,6 +139,13 @@ public class JDBC4Statement extends LimboStatement implements Statement {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* The <code>execute</code> method executes an SQL statement and indicates the
|
||||
* form of the first result. You must then use the methods
|
||||
* <code>getResultSet</code> or <code>getUpdateCount</code>
|
||||
* to retrieve the result, and <code>getMoreResults</code> to
|
||||
* move to any subsequent result(s).
|
||||
*/
|
||||
@Override
|
||||
public boolean execute(String sql) throws SQLException {
|
||||
internalClose();
|
||||
@@ -128,12 +153,14 @@ public class JDBC4Statement extends LimboStatement implements Statement {
|
||||
return this.withConnectionTimeout(
|
||||
() -> {
|
||||
try {
|
||||
// TODO: if sql is a readOnly query, do we still need the locks?
|
||||
connectionLock.lock();
|
||||
final long stmtPointer = connection.prepare(sql);
|
||||
List<Object[]> result = execute(stmtPointer);
|
||||
statement = connection.prepare(sql);
|
||||
final boolean result = statement.execute();
|
||||
updateGeneratedKeys();
|
||||
exhaustedResults = false;
|
||||
return !result.isEmpty();
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
@@ -142,10 +169,9 @@ public class JDBC4Statement extends LimboStatement implements Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SkipNullableCheck
|
||||
public ResultSet getResultSet() throws SQLException {
|
||||
// TODO
|
||||
return null;
|
||||
requireNonNull(statement, "statement is null");
|
||||
return new JDBC4ResultSet(statement.getResultSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,7 +314,7 @@ public class JDBC4Statement extends LimboStatement 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;
|
||||
}
|
||||
|
||||
@@ -297,7 +323,7 @@ public class JDBC4Statement extends LimboStatement 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;
|
||||
}
|
||||
|
||||
@@ -314,6 +340,18 @@ public class JDBC4Statement extends LimboStatement implements Statement {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void internalClose() throws SQLException {
|
||||
// TODO
|
||||
}
|
||||
|
||||
protected void clearGeneratedKeys() throws SQLException {
|
||||
// TODO
|
||||
}
|
||||
|
||||
protected void updateGeneratedKeys() throws SQLException {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private <T> T withConnectionTimeout(SQLCallable<T> callable) throws SQLException {
|
||||
final int originalBusyTimeoutMillis = connection.getBusyTimeout();
|
||||
if (queryTimeoutSeconds > 0) {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package org.github.tursodatabase.utils;
|
||||
|
||||
import static org.github.tursodatabase.utils.ByteArrayUtils.utf8ByteBufferToString;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.github.tursodatabase.LimboErrorCode;
|
||||
import org.github.tursodatabase.annotations.Nullable;
|
||||
import org.github.tursodatabase.exceptions.LimboException;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.github.tursodatabase.utils.ByteArrayUtils.utf8ByteBufferToString;
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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);");
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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.Disabled;
|
||||
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
|
||||
@Disabled("https://github.com/tursodatabase/limbo/pull/743#issuecomment-2600746904")
|
||||
void invoking_next_before_the_last_row_should_return_true() throws Exception {
|
||||
stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
|
||||
stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');");
|
||||
stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');");
|
||||
|
||||
// first call to next occur internally
|
||||
stmt.executeQuery("SELECT * FROM users");
|
||||
ResultSet resultSet = stmt.getResultSet();
|
||||
|
||||
assertTrue(resultSet.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("https://github.com/tursodatabase/limbo/pull/743#issuecomment-2600746904")
|
||||
void invoking_next_after_the_last_row_should_return_false() throws Exception {
|
||||
stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
|
||||
stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');");
|
||||
stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');");
|
||||
|
||||
// first call to next occur internally
|
||||
stmt.executeQuery("SELECT * FROM users");
|
||||
ResultSet resultSet = stmt.getResultSet();
|
||||
|
||||
while (resultSet.next()) {
|
||||
// run until next() returns false
|
||||
}
|
||||
|
||||
// if the previous call to next() returned false, consecutive call to next() should return false as well
|
||||
assertFalse(resultSet.next());
|
||||
}
|
||||
}
|
||||
@@ -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;"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user