diff --git a/.github/workflows/push_only.yml b/.github/workflows/push_only.yml
new file mode 100644
index 000000000..1c10bbb62
--- /dev/null
+++ b/.github/workflows/push_only.yml
@@ -0,0 +1,42 @@
+name: Benchmarks+Nyrkiö
+
+# Pull request support isn't integrated to the github-action-benchmark so run only post-merge
+on:
+ push:
+ branches: [ "main", "master", "notmain", "add-nyrkio" ]
+
+env:
+ CARGO_TERM_COLOR: never
+
+jobs:
+ bench:
+ runs-on: ubuntu-latest
+ environment: test
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ # cache: 'npm'
+ # - name: Install dependencies
+ # run: npm install && npm run build
+
+ - name: Bench
+ run: cargo bench 2>&1 | tee output.txt
+
+ - name: Analyze benchmark result with Nyrkiö
+ uses: nyrkio/github-action-benchmark@HEAD
+ with:
+ name: turso
+ tool: criterion
+ output-file-path: output.txt
+ fail-on-alert: true
+ # Nyrkiö configuration
+ nyrkio-enable: true
+ # Get yours from https://nyrkio.com/docs/getting-started
+ nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}
+
+ # Old way...
+ # Explicitly set this to null. We don't want threshold based alerts today.
+ external-data-json-path: null
+ gh-repository: null
diff --git a/COMPAT.md b/COMPAT.md
index fcd72b633..03798d24c 100644
--- a/COMPAT.md
+++ b/COMPAT.md
@@ -408,7 +408,7 @@ Modifiers:
| AggFinal | Yes |
| AggStep | Yes |
| AggStep | Yes |
-| And | No |
+| And | Yes |
| AutoCommit | No |
| BitAnd | Yes |
| BitNot | Yes |
@@ -501,7 +501,7 @@ Modifiers:
| OpenWrite | No |
| OpenWriteAsync | Yes |
| OpenWriteAwait | Yes |
-| Or | No |
+| Or | Yes |
| Pagecount | No |
| Param | No |
| ParseSchema | No |
diff --git a/README.md b/README.md
index 5b4b7bd30..85fb2f1ea 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
-
+
---
diff --git a/bindings/java/rs_src/limbo_connection.rs b/bindings/java/rs_src/limbo_connection.rs
index 1399d8b42..dd54e9087 100644
--- a/bindings/java/rs_src/limbo_connection.rs
+++ b/bindings/java/rs_src/limbo_connection.rs
@@ -9,16 +9,19 @@ use jni::sys::jlong;
use jni::JNIEnv;
use limbo_core::Connection;
use std::rc::Rc;
+use std::sync::Arc;
#[derive(Clone)]
#[allow(dead_code)]
pub struct LimboConnection {
+ // Because java's LimboConnection is 1:1 mapped to limbo connection, we can use Rc
pub(crate) conn: Rc,
- pub(crate) io: Rc,
+ // Because io is shared across multiple `LimboConnection`s, wrap it with Arc
+ pub(crate) io: Arc,
}
impl LimboConnection {
- pub fn new(conn: Rc, io: Rc) -> Self {
+ pub fn new(conn: Rc, io: Arc) -> Self {
LimboConnection { conn, io }
}
@@ -69,7 +72,7 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboConnection_prepar
};
match connection.conn.prepare(sql) {
- Ok(stmt) => LimboStatement::new(stmt).to_ptr(),
+ Ok(stmt) => LimboStatement::new(stmt, connection.clone()).to_ptr(),
Err(e) => {
set_err_msg_and_throw_exception(
&mut env,
diff --git a/bindings/java/rs_src/limbo_db.rs b/bindings/java/rs_src/limbo_db.rs
index 09d8afa75..16cb3d66b 100644
--- a/bindings/java/rs_src/limbo_db.rs
+++ b/bindings/java/rs_src/limbo_db.rs
@@ -5,16 +5,16 @@ use jni::objects::{JByteArray, JObject};
use jni::sys::{jint, jlong};
use jni::JNIEnv;
use limbo_core::Database;
-use std::rc::Rc;
use std::sync::Arc;
struct LimboDB {
db: Arc,
+ io: Arc,
}
impl LimboDB {
- pub fn new(db: Arc) -> Self {
- LimboDB { db }
+ pub fn new(db: Arc, io: Arc) -> Self {
+ LimboDB { db, io }
}
pub fn to_ptr(self) -> jlong {
@@ -76,14 +76,13 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB_openUtf8<'loca
}
};
- LimboDB::new(db).to_ptr()
+ LimboDB::new(db, io).to_ptr()
}
#[no_mangle]
pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB_connect0<'local>(
mut env: JNIEnv<'local>,
obj: JObject<'local>,
- file_path_byte_arr: JByteArray<'local>,
db_pointer: jlong,
) -> jlong {
let db = match to_limbo_db(db_pointer) {
@@ -94,41 +93,7 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB_connect0<'loca
}
};
- let path = match env
- .convert_byte_array(file_path_byte_arr)
- .map_err(|e| e.to_string())
- {
- Ok(bytes) => match String::from_utf8(bytes) {
- Ok(s) => s,
- Err(e) => {
- set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
- return 0;
- }
- },
- Err(e) => {
- set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
- return 0;
- }
- };
-
- let io: Rc = match path.as_str() {
- ":memory:" => match limbo_core::MemoryIO::new() {
- Ok(io) => Rc::new(io),
- Err(e) => {
- set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
- return 0;
- }
- },
- _ => match limbo_core::PlatformIO::new() {
- Ok(io) => Rc::new(io),
- Err(e) => {
- set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
- return 0;
- }
- },
- };
- let conn = LimboConnection::new(db.db.connect(), io);
-
+ let conn = LimboConnection::new(db.db.connect(), db.io.clone());
conn.to_ptr()
}
diff --git a/bindings/java/rs_src/limbo_statement.rs b/bindings/java/rs_src/limbo_statement.rs
index cdd8a5c75..7de4b2c19 100644
--- a/bindings/java/rs_src/limbo_statement.rs
+++ b/bindings/java/rs_src/limbo_statement.rs
@@ -1,5 +1,6 @@
use crate::errors::Result;
use crate::errors::{LimboError, LIMBO_ETC};
+use crate::limbo_connection::LimboConnection;
use crate::utils::set_err_msg_and_throw_exception;
use jni::objects::{JObject, JValue};
use jni::sys::jlong;
@@ -7,6 +8,7 @@ use jni::JNIEnv;
use limbo_core::{Statement, StepResult};
pub const STEP_RESULT_ID_ROW: i32 = 10;
+#[allow(dead_code)]
pub const STEP_RESULT_ID_IO: i32 = 20;
pub const STEP_RESULT_ID_DONE: i32 = 30;
pub const STEP_RESULT_ID_INTERRUPT: i32 = 40;
@@ -15,11 +17,12 @@ pub const STEP_RESULT_ID_ERROR: i32 = 60;
pub struct LimboStatement {
pub(crate) stmt: Statement,
+ pub(crate) connection: LimboConnection,
}
impl LimboStatement {
- pub fn new(stmt: Statement) -> Self {
- LimboStatement { stmt }
+ pub fn new(stmt: Statement, connection: LimboConnection) -> Self {
+ LimboStatement { stmt, connection }
}
pub fn to_ptr(self) -> jlong {
@@ -50,30 +53,38 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboStatement_step<'l
Ok(stmt) => stmt,
Err(e) => {
set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
-
- return JObject::null();
+ return to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None);
}
};
- match stmt.stmt.step() {
- Ok(StepResult::Row(row)) => match row_to_obj_array(&mut env, &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());
- to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None)
+ loop {
+ let step_result = match stmt.stmt.step() {
+ Ok(result) => result,
+ Err(_) => return to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None),
+ };
+
+ match step_result {
+ StepResult::Row(row) => {
+ return match row_to_obj_array(&mut env, &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());
+ 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) => 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());
- to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None)
+ StepResult::IO => {
+ if let Err(e) = stmt.connection.io.run_once() {
+ set_err_msg_and_throw_exception(&mut env, obj, LIMBO_ETC, e.to_string());
+ return to_limbo_step_result(&mut env, STEP_RESULT_ID_ERROR, None);
+ }
}
- },
- 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),
+ StepResult::Done => return to_limbo_step_result(&mut env, STEP_RESULT_ID_DONE, None),
+ StepResult::Interrupt => {
+ return to_limbo_step_result(&mut env, STEP_RESULT_ID_INTERRUPT, None)
+ }
+ StepResult::Busy => return to_limbo_step_result(&mut env, STEP_RESULT_ID_BUSY, None),
+ }
}
}
diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java
index 89d13b8cf..ad6ee68a0 100644
--- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java
+++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java
@@ -1,5 +1,9 @@
package org.github.tursodatabase.core;
+import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArray;
+
+import java.sql.SQLException;
+import java.util.concurrent.locks.ReentrantLock;
import org.github.tursodatabase.LimboErrorCode;
import org.github.tursodatabase.annotations.NativeInvocation;
@@ -8,12 +12,6 @@ import org.github.tursodatabase.utils.LimboExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.concurrent.locks.ReentrantLock;
-
-import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArray;
-
/**
* This class provides a thin JNI layer over the SQLite3 C API.
*/
@@ -39,7 +37,7 @@ public final class LimboDB extends AbstractDB {
* Loads the SQLite interface backend.
*/
public static void load() {
- if (isLoaded) return;
+ if (isLoaded) {return;}
try {
System.loadLibrary("_limbo_java");
@@ -49,7 +47,7 @@ public final class LimboDB extends AbstractDB {
}
/**
- * @param url e.g. "jdbc:sqlite:fileName
+ * @param url e.g. "jdbc:sqlite:fileName
* @param filePath e.g. path to file
*/
public static LimboDB create(String url, String filePath) throws SQLException {
@@ -86,7 +84,9 @@ public final class LimboDB extends AbstractDB {
byte[] filePathBytes = stringToUtf8ByteArray(filePath);
if (filePathBytes == null) {
- throw LimboExceptionUtils.buildLimboException(LimboErrorCode.LIMBO_ETC.code, "File path cannot be converted to byteArray. File name: " + filePath);
+ throw LimboExceptionUtils.buildLimboException(
+ LimboErrorCode.LIMBO_ETC.code,
+ "File path cannot be converted to byteArray. File name: " + filePath);
}
dbPointer = openUtf8(filePathBytes, openFlags);
@@ -95,14 +95,10 @@ public final class LimboDB extends AbstractDB {
@Override
public long connect() throws SQLException {
- byte[] filePathBytes = stringToUtf8ByteArray(filePath);
- if (filePathBytes == null) {
- throw LimboExceptionUtils.buildLimboException(LimboErrorCode.LIMBO_ETC.code, "File path cannot be converted to byteArray. File name: " + filePath);
- }
- return connect0(filePathBytes, dbPointer);
+ return connect0(dbPointer);
}
- private native long connect0(byte[] path, long databasePtr) throws SQLException;
+ private native long connect0(long databasePtr) throws SQLException;
@VisibleForTesting
native void throwJavaException(int errorCode) throws SQLException;
@@ -110,7 +106,7 @@ public final class LimboDB extends AbstractDB {
/**
* Throws formatted SQLException with error code and message.
*
- * @param errorCode Error code.
+ * @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation(invokedFrom = "limbo_db.rs")
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 19d730727..882d2b78b 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
@@ -64,6 +64,11 @@ public class LimboResultSet {
row++;
}
+ if (lastStepResult.isInInvalidState()) {
+ open = false;
+ throw new SQLException("step() returned invalid result: " + lastStepResult);
+ }
+
pastLastRow = lastStepResult.isDone();
if (pastLastRow) {
open = false;
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 7870cbeab..27a8dfc05 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
@@ -13,6 +13,7 @@ public class LimboStepResult {
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;
+ // Indicates that the database file could not be written because of concurrent activity by some other connection
private static final int STEP_RESULT_ID_BUSY = 50;
private static final int STEP_RESULT_ID_ERROR = 60;
@@ -41,6 +42,14 @@ public class LimboStepResult {
return stepResultId == STEP_RESULT_ID_DONE;
}
+ public boolean isInInvalidState() {
+ // current implementation doesn't allow STEP_RESULT_ID_IO to be returned
+ return stepResultId == STEP_RESULT_ID_IO ||
+ stepResultId == STEP_RESULT_ID_INTERRUPT ||
+ stepResultId == STEP_RESULT_ID_BUSY ||
+ stepResultId == STEP_RESULT_ID_ERROR;
+ }
+
@Override
public String toString() {
return "LimboStepResult{" +
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 e717232a8..88a499b9d 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
@@ -9,7 +9,6 @@ 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 {
@@ -27,7 +26,6 @@ class JDBC4ResultSetTest {
}
@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');");
@@ -41,7 +39,6 @@ class JDBC4ResultSetTest {
}
@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');");
diff --git a/core/lib.rs b/core/lib.rs
index f093762fb..d75510f7c 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -295,7 +295,6 @@ impl Connection {
pub(crate) fn run_cmd(self: &Rc, cmd: Cmd) -> Result