mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-09 19:24:21 +01:00
Merge 'Make iterate() lazily evaluated on wasm' from Diego Reis
#514 Introduces a new feature for lazy evaluation in the `Statement.raw().iterate()` method and includes related changes in both the test and implementation files. The most important changes include adding a test case for lazy evaluation, creating a `RowIterator` struct, and modifying the `iterate` method to use this new struct. Everything seems to works fine, but suggestions on code improvement and test use cases are welcoming. Closes #527
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,3 +26,6 @@ dist/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Javascript
|
||||
**/node_modules/
|
||||
1444
bindings/wasm/integration-tests/package-lock.json
generated
1444
bindings/wasm/integration-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,13 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "PROVIDER=better-sqlite3 ava tests/test.js && PROVIDER=limbo-wasm ava tests/test.js"
|
||||
"test": "PROVIDER=better-sqlite3 ava tests/test.js && PROVIDER=limbo-wasm ava tests/test.js && rm *.db *.db-wal"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^5.3.0"
|
||||
"ava": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^8.4.0",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"limbo-wasm": "../pkg"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ test.beforeEach(async (t) => {
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)
|
||||
`);
|
||||
db.exec(
|
||||
"INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')"
|
||||
"INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')"
|
||||
);
|
||||
db.exec(
|
||||
"INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')"
|
||||
"INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')"
|
||||
);
|
||||
t.context = {
|
||||
db,
|
||||
errorType,
|
||||
provider
|
||||
db,
|
||||
errorType,
|
||||
provider
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
test.serial("Statement.raw().all()", async (t) => {
|
||||
const db = t.context.db;
|
||||
@@ -31,54 +31,54 @@ test.serial("Statement.raw().all()", async (t) => {
|
||||
});
|
||||
|
||||
test.serial("Statement.raw().get()", async (t) => {
|
||||
const db = t.context.db;
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
1, "Alice", "alice@example.org"
|
||||
];
|
||||
t.deepEqual(stmt.raw().get(), expected);
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
1, "Alice", "alice@example.org"
|
||||
];
|
||||
t.deepEqual(stmt.raw().get(), expected);
|
||||
|
||||
const emptyStmt = db.prepare("SELECT * FROM users WHERE id = -1");
|
||||
t.is(emptyStmt.raw().get(), undefined);
|
||||
const emptyStmt = db.prepare("SELECT * FROM users WHERE id = -1");
|
||||
t.is(emptyStmt.raw().get(), undefined);
|
||||
});
|
||||
|
||||
test.serial("Statement.raw().iterate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
{ done: false, value: [1, "Alice", "alice@example.org"] },
|
||||
{ done: false, value: [2, "Bob", "bob@example.com"] },
|
||||
{ done: true, value: undefined },
|
||||
];
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
{ done: false, value: [1, "Alice", "alice@example.org"] },
|
||||
{ done: false, value: [2, "Bob", "bob@example.com"] },
|
||||
{ done: true, value: undefined },
|
||||
];
|
||||
|
||||
let iter = stmt.raw().iterate();
|
||||
t.is(typeof iter[Symbol.iterator], 'function');
|
||||
t.deepEqual(iter.next(), expected[0])
|
||||
t.deepEqual(iter.next(), expected[1])
|
||||
t.deepEqual(iter.next(), expected[2])
|
||||
let iter = stmt.raw().iterate();
|
||||
t.is(typeof iter[Symbol.iterator], 'function');
|
||||
t.deepEqual(iter.next(), expected[0])
|
||||
t.deepEqual(iter.next(), expected[1])
|
||||
t.deepEqual(iter.next(), expected[2])
|
||||
|
||||
const emptyStmt = db.prepare("SELECT * FROM users WHERE id = -1");
|
||||
t.is(typeof emptyStmt[Symbol.iterator], 'undefined');
|
||||
t.throws(() => emptyStmt.next(), { instanceOf: TypeError });
|
||||
const emptyStmt = db.prepare("SELECT * FROM users WHERE id = -1");
|
||||
t.is(typeof emptyStmt[Symbol.iterator], 'undefined');
|
||||
t.throws(() => emptyStmt.next(), { instanceOf: TypeError });
|
||||
});
|
||||
|
||||
const connect = async (path_opt) => {
|
||||
const path = path_opt ?? "hello.db";
|
||||
const provider = process.env.PROVIDER;
|
||||
if (provider === "limbo-wasm") {
|
||||
const database = process.env.LIBSQL_DATABASE ?? path;
|
||||
const x = await import("limbo-wasm");
|
||||
const options = {};
|
||||
const db = new x.Database(database, options);
|
||||
return [db, x.SqliteError, provider];
|
||||
const database = process.env.LIBSQL_DATABASE ?? path;
|
||||
const x = await import("limbo-wasm");
|
||||
const options = {};
|
||||
const db = new x.Database(database, options);
|
||||
return [db, x.SqliteError, provider];
|
||||
}
|
||||
if (provider == "better-sqlite3") {
|
||||
const x = await import("better-sqlite3");
|
||||
const options = {};
|
||||
const db = x.default(path, options);
|
||||
return [db, x.SqliteError, provider];
|
||||
const x = await import("better-sqlite3");
|
||||
const options = {};
|
||||
const db = x.default(path, options);
|
||||
return [db, x.SqliteError, provider];
|
||||
}
|
||||
throw new Error("Unknown provider: " + provider);
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
use js_sys::{Array, Object};
|
||||
use limbo_core::{
|
||||
maybe_init_database_file, BufferPool, OpenFlags, Pager, Result, WalFile, WalFileShared,
|
||||
};
|
||||
@@ -55,6 +56,38 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct RowIterator {
|
||||
inner: RefCell<limbo_core::Statement>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RowIterator {
|
||||
fn new(inner: RefCell<limbo_core::Statement>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn next(&mut self) -> JsValue {
|
||||
match self.inner.borrow_mut().step() {
|
||||
Ok(limbo_core::RowResult::Row(row)) => {
|
||||
let row_array = Array::new();
|
||||
for value in row.values {
|
||||
let value = to_js_value(value);
|
||||
row_array.push(&value);
|
||||
}
|
||||
JsValue::from(row_array)
|
||||
}
|
||||
Ok(limbo_core::RowResult::IO) => JsValue::UNDEFINED,
|
||||
Ok(limbo_core::RowResult::Done) | Ok(limbo_core::RowResult::Interrupt) => {
|
||||
JsValue::UNDEFINED
|
||||
}
|
||||
Ok(limbo_core::RowResult::Busy) => JsValue::UNDEFINED,
|
||||
Err(e) => panic!("Error: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Statement {
|
||||
inner: RefCell<limbo_core::Statement>,
|
||||
@@ -113,16 +146,35 @@ impl Statement {
|
||||
array
|
||||
}
|
||||
|
||||
pub fn iterate(&self) -> JsValue {
|
||||
let all = self.all();
|
||||
let iterator_fn = js_sys::Reflect::get(&all, &js_sys::Symbol::iterator())
|
||||
.expect("Failed to get iterator function")
|
||||
.dyn_into::<js_sys::Function>()
|
||||
.expect("Symbol.iterator is not a function");
|
||||
#[wasm_bindgen]
|
||||
pub fn iterate(self) -> JsValue {
|
||||
let iterator = RowIterator::new(self.inner);
|
||||
let iterator_obj = Object::new();
|
||||
|
||||
iterator_fn
|
||||
.call0(&all)
|
||||
.expect("Failed to call iterator function")
|
||||
// Define the next method that will be called by JavaScript
|
||||
let next_fn = js_sys::Function::new_with_args(
|
||||
"",
|
||||
"const value = this.iterator.next();
|
||||
const done = value === undefined;
|
||||
return {
|
||||
value,
|
||||
done
|
||||
};",
|
||||
);
|
||||
|
||||
js_sys::Reflect::set(&iterator_obj, &JsValue::from_str("next"), &next_fn).unwrap();
|
||||
|
||||
js_sys::Reflect::set(
|
||||
&iterator_obj,
|
||||
&JsValue::from_str("iterator"),
|
||||
&JsValue::from(iterator),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let symbol_iterator = js_sys::Function::new_no_args("return this;");
|
||||
js_sys::Reflect::set(&iterator_obj, &js_sys::Symbol::iterator(), &symbol_iterator).unwrap();
|
||||
|
||||
JsValue::from(iterator_obj)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user