From 73f8eab65117a41bc134c3f32c3e5c077064754a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 19 Jan 2025 11:48:45 +0900 Subject: [PATCH] Remove the tight coupling(using inheritance) between LimboXXX and JDBCXXX and favor composition instead --- bindings/java/rs_src/limbo_statement.rs | 44 +++++++++++--- .../annotations/NativeInvocation.java | 2 +- .../tursodatabase/core/LimboConnection.java | 4 +- .../tursodatabase/core/LimboResultSet.java | 42 +++++++++++--- .../tursodatabase/core/LimboStatement.java | 57 +++++++------------ .../tursodatabase/core/LimboStepResult.java | 25 ++++++++ .../tursodatabase/jdbc4/JDBC4ResultSet.java | 9 +-- .../tursodatabase/jdbc4/JDBC4Statement.java | 33 +++++++---- 8 files changed, 148 insertions(+), 68 deletions(-) create mode 100644 bindings/java/src/main/java/org/github/tursodatabase/core/LimboStepResult.java diff --git a/bindings/java/rs_src/limbo_statement.rs b/bindings/java/rs_src/limbo_statement.rs index 78eff1fc4..e0e1274e0 100644 --- a/bindings/java/rs_src/limbo_statement.rs +++ b/bindings/java/rs_src/limbo_statement.rs @@ -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,23 @@ 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(), + _ => 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 +100,27 @@ fn row_to_obj_array<'local>( Ok(obj_array.into()) } + +fn to_limbo_step_result<'local>( + env: &mut JNIEnv<'local>, + id: i32, + result: Option>, +) -> 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, + ) + .unwrap_or_else(|_| JObject::null()) + } else { + env.new_object( + "org/github/tursodatabase/core/LimboStepResult", + "(I[Ljava/lang/Object;)V", + &ctor_args, + ) + .unwrap_or_else(|_| JObject::null()) + } +} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/annotations/NativeInvocation.java b/bindings/java/src/main/java/org/github/tursodatabase/annotations/NativeInvocation.java index d3a905608..90afdea6d 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/annotations/NativeInvocation.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/annotations/NativeInvocation.java @@ -10,6 +10,6 @@ import java.lang.annotation.Target; * Annotation to mark methods that are called by native functions. */ @Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface NativeInvocation { } diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java index d6c3ab6af..15577922d 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboConnection.java @@ -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(prepareUtf8(connectionPtr, sqlBytes)); } private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException; 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 5d8b495ba..fd2845a17 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 @@ -3,25 +3,51 @@ package org.github.tursodatabase.core; import java.sql.SQLException; /** - * JDBC ResultSet. + * A table of data representing limbo database result set, which is generated by executing a statement that queries the + * database. + *

+ * A {@link LimboResultSet} object is automatically closed when the {@link LimboStatement} object that generated it is + * closed or re-executed. */ -public abstract class LimboResultSet { +public class LimboResultSet { - protected final LimboStatement statement; + 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; // 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; - protected LimboResultSet(LimboStatement statement) { + private boolean pastLastRow = false; + + public static LimboResultSet of(LimboStatement statement) { + return new LimboResultSet(statement); + } + + private LimboResultSet(LimboStatement statement) { + this.open = true; this.statement = statement; } + public boolean next() throws SQLException { + if (!open || isEmptyResultSet || pastLastRow) { + return false; // completed ResultSet + } + + if (maxRows != 0 && row == maxRows) { + return false; + } + + // TODO + // int statusCode = this.statement.step(); + this.statement.step(); + return true; + } + /** * Checks the status of the result set. * @@ -34,7 +60,7 @@ 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"); } 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 592593df4..8dc6e3346 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 @@ -2,58 +2,45 @@ package org.github.tursodatabase.core; 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 java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -public abstract class LimboStatement { +/** + * 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 + * have been generated by different LimboStatement objects. All execution method in the LimboStatement + * implicitly close the current resultSet object of the statement if an open one exists. + */ +public class LimboStatement { - protected final LimboConnection connection; - protected final LimboResultSet resultSet; + private final long statementPointer; + private final LimboResultSet resultSet; @Nullable protected String sql = null; - protected LimboStatement(LimboConnection connection) { - this.connection = connection; - this.resultSet = new JDBC4ResultSet(this); + public LimboStatement(long statementPointer) { + this.statementPointer = statementPointer; + this.resultSet = LimboResultSet.of(this); } - protected void internalClose() throws SQLException { - // TODO + public LimboResultSet resultSet() { + return resultSet; } - protected void clearGeneratedKeys() throws SQLException { - // TODO + public void execute() throws SQLException { + LimboResultSet result = LimboResultSet.of(this); + + // at least, run query minimally + result.next(); } - protected void updateGeneratedKeys() throws SQLException { - // TODO + public LimboStepResult step() throws SQLException { + return step(this.statementPointer); } - // 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 execute(long stmtPointer) throws SQLException { - List 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); - } - - return result; - } - - private native Object[] step(long stmtPointer) throws SQLException; + private native LimboStepResult step(long stmtPointer) throws SQLException; /** * Throws formatted SQLException with error code and message. 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 new file mode 100644 index 000000000..5a33920b7 --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboStepResult.java @@ -0,0 +1,25 @@ +package org.github.tursodatabase.core; + +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; + + // Identifier for limbo's StepResult + private final int stepResultId; + private final Object[] result; + + @NativeInvocation + public LimboStepResult(int stepResultId, Object[] result) { + this.stepResultId = stepResultId; + this.result = 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 07bf6ed92..69b51a8f0 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 @@ -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,10 +11,12 @@ 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 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 4a06d20a1..31726a5d9 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,18 +1,18 @@ package org.github.tursodatabase.jdbc4; +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.ArrayList; -import java.util.List; import java.util.concurrent.locks.ReentrantLock; -/** - * Implementation of the {@link Statement} interface for JDBC 4. - */ -public class JDBC4Statement extends LimboStatement implements Statement { +public class JDBC4Statement implements Statement { + + private final LimboConnection connection; + @Nullable + private LimboStatement statement = null; private boolean closed; private boolean closeOnCompletion; @@ -32,7 +32,7 @@ public class JDBC4Statement extends LimboStatement implements Statement { } public JDBC4Statement(LimboConnection connection, int resultSetType, int resultSetConcurrency, int resultSetHoldability) { - super(connection); + this.connection = connection; this.resultSetType = resultSetType; this.resultSetConcurrency = resultSetConcurrency; this.resultSetHoldability = resultSetHoldability; @@ -129,11 +129,12 @@ public class JDBC4Statement extends LimboStatement implements Statement { () -> { try { connectionLock.lock(); - final long stmtPointer = connection.prepare(sql); - List result = execute(stmtPointer); + statement = connection.prepare(sql); + statement.execute(); updateGeneratedKeys(); exhaustedResults = false; - return !result.isEmpty(); + return true; + // return !result.isEmpty(); } finally { connectionLock.unlock(); } @@ -314,6 +315,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 withConnectionTimeout(SQLCallable callable) throws SQLException { final int originalBusyTimeoutMillis = connection.getBusyTimeout(); if (queryTimeoutSeconds > 0) {