diff --git a/bindings/javascript/README.md b/bindings/javascript/README.md
new file mode 100644
index 000000000..652723e14
--- /dev/null
+++ b/bindings/javascript/README.md
@@ -0,0 +1,36 @@
+# Limbo
+
+Limbo is a _work-in-progress_, in-process OLTP database engine library written in Rust that has:
+
+* **Asynchronous I/O** support on Linux with `io_uring`
+* **SQLite compatibility** [[doc](COMPAT.md)] for SQL dialect, file formats, and the C API
+* **Language bindings** for JavaScript/WebAssembly, Rust, Go, Python, and [Java](bindings/java)
+* **OS support** for Linux, macOS, and Windows
+
+In the future, we will be also working on:
+
+* **Integrated vector search** for embeddings and vector similarity.
+* **`BEGIN CONCURRENT`** for improved write throughput.
+* **Improved schema management** including better `ALTER` support and strict column types by default.
+
+## Install
+
+```sh
+npm i @tursodatabase/limbo
+```
+
+## Usage
+
+```js
+import { Database } from 'limbo-wasm';
+
+const db = new Database('sqlite.db');
+const stmt = db.prepare('SELECT * FROM users');
+const users = stmt.all();
+console.log(users);
+```
+
+## Documentation
+
+- [API Docs](https://github.com/tursodatabase/limbo/blob/main/bindings/javascript/docs/API.md)
+
diff --git a/bindings/javascript/__test__/better-sqlite3.spec.mjs b/bindings/javascript/__test__/better-sqlite3.spec.mjs
index 7a8c3ee37..318edfaa8 100644
--- a/bindings/javascript/__test__/better-sqlite3.spec.mjs
+++ b/bindings/javascript/__test__/better-sqlite3.spec.mjs
@@ -47,12 +47,23 @@ test("Statement.run() returns correct result object", async (t) => {
test("Statment.iterate() should correctly return an iterable object", async (t) => {
const [db] = await connect(":memory:");
- db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
- db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
- db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
+ db.prepare(
+ "CREATE TABLE users (name TEXT, age INTEGER, nationality TEXT)",
+ ).run();
+ db.prepare("INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)").run(
+ ["Alice", 42],
+ "UK",
+ );
+ db.prepare("INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)").run(
+ "Bob",
+ 24,
+ "USA",
+ );
+
let rows = db.prepare("SELECT * FROM users").iterate();
for (const row of rows) {
t.truthy(row.name);
+ t.truthy(row.nationality);
t.true(typeof row.age === "number");
}
});
@@ -67,18 +78,17 @@ test("Empty prepared statement should throw", async (t) => {
);
});
-test("Test pragma", async (t) => {
+test("Test pragma()", async (t) => {
const [db] = await connect(":memory:");
t.deepEqual(typeof db.pragma("cache_size")[0].cache_size, "number");
t.deepEqual(typeof db.pragma("cache_size", { simple: true }), "number");
});
-test("Test bind()", async (t) => {
+test("Statement shouldn't bind twice with bind()", async (t) => {
const [db] = await connect(":memory:");
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
let stmt = db.prepare("SELECT * FROM users WHERE name = ?").bind("Alice");
- console.log(db.prepare("SELECT * FROM users").raw().get());
for (const row of stmt.iterate()) {
t.truthy(row.name);
@@ -93,7 +103,98 @@ test("Test bind()", async (t) => {
);
});
-test("Test exec()", async (t) => {
+test("Test pluck(): Rows should only have the values of the first column", async (t) => {
+ const [db] = await connect(":memory:");
+ db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
+
+ let stmt = db.prepare("SELECT * FROM users").pluck();
+
+ for (const row of stmt.iterate()) {
+ t.truthy(row);
+ t.assert(typeof row === "string");
+ }
+});
+
+test("Test raw(): Rows should be returned as arrays", async (t) => {
+ const [db] = await connect(":memory:");
+ db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
+
+
+ let stmt = db.prepare("SELECT * FROM users").raw();
+
+ for (const row of stmt.iterate()) {
+ t.true(Array.isArray(row));
+ t.true(typeof row[0] === "string");
+ t.true(typeof row[1] === "number");
+ }
+
+ stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
+ const row = stmt.get("Alice");
+ t.true(Array.isArray(row));
+ t.is(row.length, 2);
+ t.is(row[0], "Alice");
+ t.is(row[1], 42);
+
+ const noRow = stmt.get("Charlie");
+ t.is(noRow, undefined);
+
+ stmt = db.prepare("SELECT * FROM users").raw();
+ const rows = stmt.all();
+ t.true(Array.isArray(rows));
+ t.is(rows.length, 2);
+ t.deepEqual(rows[0], ["Alice", 42]);
+ t.deepEqual(rows[1], ["Bob", 24]);
+});
+
+
+test("Presentation modes should be mutually exclusive", async (t) => {
+ const [db] = await connect(":memory:");
+ db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
+
+
+ // test raw()
+ let stmt = db.prepare("SELECT * FROM users").pluck().raw();
+
+ for (const row of stmt.iterate()) {
+ t.true(Array.isArray(row));
+ t.true(typeof row[0] === "string");
+ t.true(typeof row[1] === "number");
+ }
+
+ stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
+ const row = stmt.get("Alice");
+ t.true(Array.isArray(row));
+ t.is(row.length, 2);
+ t.is(row[0], "Alice");
+ t.is(row[1], 42);
+
+ const noRow = stmt.get("Charlie");
+ t.is(noRow, undefined);
+
+ stmt = db.prepare("SELECT * FROM users").raw();
+ const rows = stmt.all();
+ t.true(Array.isArray(rows));
+ t.is(rows.length, 2);
+ t.deepEqual(rows[0], ["Alice", 42]);
+ t.deepEqual(rows[1], ["Bob", 24]);
+
+ // test pluck()
+ stmt = db.prepare("SELECT * FROM users").raw().pluck();
+
+ for (const name of stmt.iterate()) {
+ t.truthy(name);
+ t.assert(typeof name === "string");
+ }
+});
+
+
+test("Test exec(): Should correctly load multiple statements from file", async (t) => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
diff --git a/bindings/javascript/__test__/limbo.spec.mjs b/bindings/javascript/__test__/limbo.spec.mjs
index 049a9e1d9..9cd3015c6 100644
--- a/bindings/javascript/__test__/limbo.spec.mjs
+++ b/bindings/javascript/__test__/limbo.spec.mjs
@@ -31,7 +31,7 @@ test("Statement.get() returns data", async (t) => {
t.is(result2["1"], 1);
});
-test("Statement.get() returns null when no data", async (t) => {
+test("Statement.get() returns undefined when no data", async (t) => {
const [db] = await connect(":memory:");
const stmt = db.prepare("SELECT 1 WHERE 1 = 2");
const result = stmt.get();
@@ -53,11 +53,11 @@ test("Statment.iterate() should correctly return an iterable object", async (t)
db.prepare(
"CREATE TABLE users (name TEXT, age INTEGER, nationality TEXT)",
).run();
- db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run(
+ db.prepare("INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)").run(
["Alice", 42],
"UK",
);
- db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run(
+ db.prepare("INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)").run(
"Bob",
24,
"USA",
@@ -66,6 +66,7 @@ test("Statment.iterate() should correctly return an iterable object", async (t)
let rows = db.prepare("SELECT * FROM users").iterate();
for (const row of rows) {
t.truthy(row.name);
+ t.truthy(row.nationality);
t.true(typeof row.age === "number");
}
});
@@ -86,7 +87,7 @@ test("Test pragma()", async (t) => {
t.true(typeof db.pragma("cache_size", { simple: true }) === "number");
});
-test("Statement binded with bind() shouldn't be binded again", async (t) => {
+test("Statement shouldn't bind twice with bind()", async (t) => {
const [db] = await connect(":memory:");
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
@@ -110,23 +111,23 @@ test("Test pluck(): Rows should only have the values of the first column", async
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
- let stmt = db.prepare("SELECT * FROM users").raw().pluck();
+
+ let stmt = db.prepare("SELECT * FROM users").pluck();
for (const row of stmt.iterate()) {
- t.truthy(row.name);
- t.true(typeof row.age === "undefined");
+ t.truthy(row);
+ t.assert(typeof row === "string");
}
});
-
-test("Test raw()", async (t) => {
+test("Test raw(): Rows should be returned as arrays", async (t) => {
const [db] = await connect(":memory:");
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
-
- // Pluck and raw should be exclusive
- let stmt = db.prepare("SELECT * FROM users").pluck().raw();
+
+
+ let stmt = db.prepare("SELECT * FROM users").raw();
for (const row of stmt.iterate()) {
t.true(Array.isArray(row));
@@ -152,8 +153,49 @@ test("Test raw()", async (t) => {
t.deepEqual(rows[1], ["Bob", 24]);
});
+test("Presentation modes should be mutually exclusive", async (t) => {
+ const [db] = await connect(":memory:");
+ db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
+ db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
-test("Test exec()", async (t) => {
+
+ // test raw()
+ let stmt = db.prepare("SELECT * FROM users").pluck().raw();
+
+ for (const row of stmt.iterate()) {
+ t.true(Array.isArray(row));
+ t.true(typeof row[0] === "string");
+ t.true(typeof row[1] === "number");
+ }
+
+ stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
+ const row = stmt.get("Alice");
+ t.true(Array.isArray(row));
+ t.is(row.length, 2);
+ t.is(row[0], "Alice");
+ t.is(row[1], 42);
+
+ const noRow = stmt.get("Charlie");
+ t.is(noRow, undefined);
+
+ stmt = db.prepare("SELECT * FROM users").raw();
+ const rows = stmt.all();
+ t.true(Array.isArray(rows));
+ t.is(rows.length, 2);
+ t.deepEqual(rows[0], ["Alice", 42]);
+ t.deepEqual(rows[1], ["Bob", 24]);
+
+ // test pluck()
+ stmt = db.prepare("SELECT * FROM users").raw().pluck();
+
+ for (const name of stmt.iterate()) {
+ t.truthy(name);
+ t.assert(typeof name === "string");
+ }
+});
+
+test("Test exec(): Should correctly load multiple statements from file", async (t) => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
diff --git a/bindings/javascript/docs/api.md b/bindings/javascript/docs/API.md
similarity index 68%
rename from bindings/javascript/docs/api.md
rename to bindings/javascript/docs/API.md
index a89235bda..b3aa865d9 100644
--- a/bindings/javascript/docs/api.md
+++ b/bindings/javascript/docs/API.md
@@ -28,8 +28,6 @@ Prepares a SQL statement for execution.
The function returns a `Statement` object.
-This function is currently not supported.
-
### transaction(function) ⇒ function
Returns a function that runs the given function in a transaction.
@@ -38,11 +36,21 @@ Returns a function that runs the given function in a transaction.
| -------- | --------------------- | ------------------------------------- |
| function | function | The function to run in a transaction. |
-This function is currently not supported.
-
### pragma(string, [options]) ⇒ results
-This function is currently not supported.
+Executes the given PRAGMA and returns its results.
+
+| Param | Type | Description |
+| -------- | --------------------- | ----------------------|
+| source | string | Pragma to be executed |
+| options | object | Options. |
+
+Most PRAGMA return a single value, the `simple: boolean` option is provided to return the first column of the first row.
+
+```js
+db.pragma('cache_size = 32000');
+console.log(db.pragma('cache_size', { simple: true })); // => 32000
+```
### backup(destination, [options]) ⇒ promise
@@ -68,7 +76,9 @@ This function is currently not supported.
Loads a SQLite3 extension
-This function is currently not supported.
+| Param | Type | Description |
+| ------ | ------------------- | -----------------------------------------|
+| path | string | The path to the extention to be loaded. |
### exec(sql) ⇒ this
@@ -78,7 +88,7 @@ Executes a SQL statement.
| ------ | ------------------- | ------------------------------------ |
| sql | string | The SQL statement string to execute. |
-This function is currently not supported.
+This can execute strings that contain multiple SQL statements.
### interrupt() ⇒ this
@@ -92,15 +102,15 @@ This function is currently not supported.
Closes the database connection.
-This function is currently not supported.
-
# class Statement
## Methods
### run([...bindParameters]) ⇒ object
-Executes the SQL statement and returns an info object.
+Executes the SQL statement and (currently) returns an array with results.
+
+**Note:** It should return an info object.
| Param | Type | Description |
| -------------- | ----------------------------- | ------------------------------------------------ |
@@ -118,8 +128,6 @@ Executes the SQL statement and returns the first row.
| -------------- | ----------------------------- | ------------------------------------------------ |
| bindParameters | array of objects | The bind parameters for executing the statement. |
-This function is currently not supported.
-
### all([...bindParameters]) ⇒ array of rows
Executes the SQL statement and returns an array of the resulting rows.
@@ -128,8 +136,6 @@ Executes the SQL statement and returns an array of the resulting rows.
| -------------- | ----------------------------- | ------------------------------------------------ |
| bindParameters | array of objects | The bind parameters for executing the statement. |
-This function is currently not supported.
-
### iterate([...bindParameters]) ⇒ iterator
Executes the SQL statement and returns an iterator to the resulting rows.
@@ -138,11 +144,21 @@ Executes the SQL statement and returns an iterator to the resulting rows.
| -------------- | ----------------------------- | ------------------------------------------------ |
| bindParameters | array of objects | The bind parameters for executing the statement. |
-This function is currently not supported.
-
### pluck([toggleState]) ⇒ this
-This function is currently not supported.
+Makes the prepared statement only return the value of the first column of any rows that it retrieves.
+
+| Param | Type | Description |
+| -------------- | ----------------------------- | ------------------------------------------------ |
+| pluckMode | boolean | Enable of disable pluck mode. If you don't pass the paramenter, pluck mode is enabled. |
+
+```js
+stmt.pluck(); // plucking ON
+stmt.pluck(true); // plucking ON
+stmt.pluck(false); // plucking OFF
+```
+
+> NOTE: When plucking is turned on, raw mode is turned off (they are mutually exclusive options).
### expand([toggleState]) ⇒ this
@@ -150,7 +166,7 @@ This function is currently not supported.
### raw([rawMode]) ⇒ this
-Toggle raw mode.
+Makes the prepared statement return rows as arrays instead of objects.
| Param | Type | Description |
| ------- | -------------------- | --------------------------------------------------------------------------------- |
@@ -158,7 +174,13 @@ Toggle raw mode.
This function enables or disables raw mode. Prepared statements return objects by default, but if raw mode is enabled, the functions return arrays instead.
-This function is currently not supported.
+```js
+stmt.raw(); // raw mode ON
+stmt.raw(true); // raw mode ON
+stmt.raw(false); // raw mode OFF
+```
+
+> NOTE: When raw mode is turned on, plucking is turned off (they are mutually exclusive options).
### columns() ⇒ array of objects
@@ -168,4 +190,8 @@ This function is currently not supported.
### bind([...bindParameters]) ⇒ this
-This function is currently not supported.
+| Param | Type | Description |
+| -------------- | ----------------------------- | ------------------------------------------------ |
+| bindParameters | array of objects | The bind parameters for executing the statement. |
+
+Binds **permanently** the given parameters to the statement. After a statement's parameters are bound this way, you may no longer provide it with execution-specific (temporary) bound parameters.
\ No newline at end of file
diff --git a/bindings/javascript/docs/CONTRIBUTING.md b/bindings/javascript/docs/CONTRIBUTING.md
new file mode 100644
index 000000000..27b071dbb
--- /dev/null
+++ b/bindings/javascript/docs/CONTRIBUTING.md
@@ -0,0 +1,31 @@
+# Contributing
+
+So you want to contribute to Limbo's binding for the ~second~ best language in the world? Awesome.
+
+First things first you'll need to install [napi-rs](https://napi.rs/), follow the instructions [here](https://napi.rs/docs/introduction/getting-started) althought is highly recommended to use `yarn` with:
+
+```sh
+yarn global add @napi-rs/cli
+```
+
+Run `yarn build` to build our napi project and run `yarn test` to run our test suite, if nothing breaks you're ready to start!
+
+## API
+
+You can check the API docs [here](./API.md), it aims to be fully compatible with [better-sqlite](https://github.com/WiseLibs/better-sqlite3/) and borrows some things from [libsql](https://github.com/tursodatabase/libsql-js). So if you find some incompability in behaviour and/or lack of functions/attributes, that's an issue and you should work on it for a great good :)
+
+## Code Structure
+
+The Rust code for the bind is on [lib.rs](../src/lib.rs). It's exposed to JS users through [wrapper](../wrapper.js), where you can
+use some JS' ~weirdness~ facilities, for instance, since Rust doesn't have variadic functions the wrapper enables us to "normalize" `bindParameters` into an array.
+
+All tests should be within the [__test__](../__test__/) folder.
+
+# Before open a PR
+
+Please be assured that:
+
+- Your fix/feature has a test checking the new behaviour;
+- Your code follows Rust's conventions with `cargo fmt`;
+- If applicable, update the [API docs](./API.md) to match the current implementation;
+
diff --git a/bindings/javascript/src/lib.rs b/bindings/javascript/src/lib.rs
index f587d6ccb..315cd9d5e 100644
--- a/bindings/javascript/src/lib.rs
+++ b/bindings/javascript/src/lib.rs
@@ -9,7 +9,6 @@ use std::sync::Arc;
use limbo_core::types::Text;
use limbo_core::{maybe_init_database_file, LimboError, StepResult};
use napi::iterator::Generator;
-use napi::JsObject;
use napi::{bindgen_prelude::ObjectFinalize, Env, JsUnknown};
use napi_derive::napi;
@@ -202,6 +201,13 @@ impl Database {
}
}
+#[derive(Debug, Clone)]
+enum PresentationMode {
+ Raw,
+ Pluck,
+ None,
+}
+
#[napi]
#[derive(Clone)]
pub struct Statement {
@@ -216,8 +222,7 @@ pub struct Statement {
pub source: String,
database: Database,
- raw: bool,
- pluck: bool,
+ presentation_mode: PresentationMode,
binded: bool,
inner: Rc>,
}
@@ -229,9 +234,8 @@ impl Statement {
inner: Rc::new(inner),
database,
source,
- pluck: false,
+ presentation_mode: PresentationMode::None,
binded: false,
- raw: false,
}
}
@@ -244,43 +248,40 @@ impl Statement {
limbo_core::StepResult::Row => {
let row = stmt.row().unwrap();
- if self.raw {
- assert!(!self.pluck, "Cannot use raw mode with pluck mode");
+ match self.presentation_mode {
+ PresentationMode::Raw => {
+ let mut raw_obj = env.create_array(row.len() as u32)?;
+ for (idx, value) in row.get_values().enumerate() {
+ let js_value = to_js_value(&env, value);
- let mut raw_obj = env.create_array(row.len() as u32)?;
- for (idx, value) in row.get_values().enumerate() {
- let js_value = to_js_value(&env, value);
+ raw_obj.set(idx as u32, js_value)?;
+ }
- raw_obj.set(idx as u32, js_value)?;
+ Ok(raw_obj.coerce_to_object()?.into_unknown())
}
+ PresentationMode::Pluck => {
+ let (_, value) =
+ row.get_values().enumerate().next().ok_or(napi::Error::new(
+ napi::Status::GenericFailure,
+ "Pluck mode requires at least one column in the result",
+ ))?;
+ let js_value = to_js_value(&env, value)?;
- return Ok(raw_obj.coerce_to_object()?.into_unknown());
+ Ok(js_value)
+ }
+ PresentationMode::None => {
+ let mut obj = env.create_object()?;
+
+ for (idx, value) in row.get_values().enumerate() {
+ let key = stmt.get_column_name(idx);
+ let js_value = to_js_value(&env, value);
+
+ obj.set_named_property(&key, js_value)?;
+ }
+
+ Ok(obj.into_unknown())
+ }
}
-
- let mut obj = env.create_object()?;
- if self.pluck {
- assert!(!self.raw, "Cannot use pluck mode with raw mode");
-
- let (idx, value) =
- row.get_values().enumerate().next().ok_or(napi::Error::new(
- napi::Status::GenericFailure,
- "Pluck mode requires at least one column in the result",
- ))?;
- let key = stmt.get_column_name(idx);
- let js_value = to_js_value(&env, value);
- obj.set_named_property(&key, js_value)?;
-
- return Ok(obj.into_unknown());
- }
-
- for (idx, value) in row.get_values().enumerate() {
- let key = stmt.get_column_name(idx);
- let js_value = to_js_value(&env, value);
-
- obj.set_named_property(&key, js_value)?;
- }
-
- Ok(obj.into_unknown())
}
limbo_core::StepResult::Done => Ok(env.get_undefined()?.into_unknown()),
limbo_core::StepResult::IO => todo!(),
@@ -305,20 +306,11 @@ impl Statement {
args: Option>,
) -> napi::Result {
self.check_and_bind(args)?;
- if self.raw {
- assert!(!self.pluck, "Cannot use raw mode with pluck mode");
- }
-
- if self.pluck {
- assert!(!self.raw, "Cannot use pluck mode with raw mode");
- }
-
Ok(IteratorStatement {
stmt: Rc::clone(&self.inner),
database: self.database.clone(),
env,
- pluck: self.pluck,
- raw: self.raw,
+ presentation_mode: self.presentation_mode.clone(),
})
}
@@ -340,44 +332,40 @@ impl Statement {
match stmt.step().map_err(into_napi_error)? {
limbo_core::StepResult::Row => {
let row = stmt.row().unwrap();
- let mut obj = env.create_object()?;
- if self.raw {
- assert!(!self.pluck, "Cannot use raw mode with pluck mode");
-
- let mut raw_array = env.create_array(row.len() as u32)?;
- for (idx, value) in row.get_values().enumerate() {
- let js_value = to_js_value(&env, value)?;
- raw_array.set(idx as u32, js_value)?;
+ match self.presentation_mode {
+ PresentationMode::Raw => {
+ let mut raw_array = env.create_array(row.len() as u32)?;
+ for (idx, value) in row.get_values().enumerate() {
+ let js_value = to_js_value(&env, value)?;
+ raw_array.set(idx as u32, js_value)?;
+ }
+ results.set_element(index, raw_array.coerce_to_object()?)?;
+ index += 1;
+ continue;
+ }
+ PresentationMode::Pluck => {
+ let (_, value) =
+ row.get_values().enumerate().next().ok_or(napi::Error::new(
+ napi::Status::GenericFailure,
+ "Pluck mode requires at least one column in the result",
+ ))?;
+ let js_value = to_js_value(&env, value)?;
+ results.set_element(index, js_value)?;
+ index += 1;
+ continue;
+ }
+ PresentationMode::None => {
+ let mut obj = env.create_object()?;
+ for (idx, value) in row.get_values().enumerate() {
+ let key = stmt.get_column_name(idx);
+ let js_value = to_js_value(&env, value);
+ obj.set_named_property(&key, js_value)?;
+ }
+ results.set_element(index, obj)?;
+ index += 1;
}
- results.set_element(index, raw_array.coerce_to_object()?)?;
- index += 1;
- continue;
}
-
- if self.pluck {
- assert!(!self.raw, "Cannot use pluck mode with raw mode");
-
- let (idx, value) =
- row.get_values().enumerate().next().ok_or(napi::Error::new(
- napi::Status::GenericFailure,
- "Pluck mode requires at least one column in the result",
- ))?;
- let key = stmt.get_column_name(idx);
- let js_value = to_js_value(&env, value)?;
- obj.set_named_property(&key, js_value)?;
- results.set_element(index, obj)?;
- index += 1;
- continue;
- }
-
- for (idx, value) in row.get_values().enumerate() {
- let key = stmt.get_column_name(idx);
- let js_value = to_js_value(&env, value);
- obj.set_named_property(&key, js_value)?;
- }
- results.set_element(index, obj)?;
- index += 1;
}
limbo_core::StepResult::Done => {
break;
@@ -400,11 +388,10 @@ impl Statement {
#[napi]
pub fn pluck(&mut self, pluck: Option) {
if let Some(false) = pluck {
- self.pluck = false;
+ self.presentation_mode = PresentationMode::None;
}
- self.raw = false;
- self.pluck = true;
+ self.presentation_mode = PresentationMode::Pluck;
}
#[napi]
@@ -415,11 +402,10 @@ impl Statement {
#[napi]
pub fn raw(&mut self, raw: Option) {
if let Some(false) = raw {
- self.raw = false;
+ self.presentation_mode = PresentationMode::None;
}
- self.pluck = false;
- self.raw = true;
+ self.presentation_mode = PresentationMode::Raw;
}
#[napi]
@@ -466,12 +452,11 @@ pub struct IteratorStatement {
stmt: Rc>,
database: Database,
env: Env,
- pluck: bool,
- raw: bool,
+ presentation_mode: PresentationMode,
}
impl Generator for IteratorStatement {
- type Yield = JsObject;
+ type Yield = JsUnknown;
type Next = ();
@@ -483,43 +468,37 @@ impl Generator for IteratorStatement {
match stmt.step().ok()? {
limbo_core::StepResult::Row => {
let row = stmt.row().unwrap();
- let mut js_row = self.env.create_object().ok()?;
- if self.raw {
- assert!(!self.pluck, "Cannot use raw mode with pluck mode");
+ match self.presentation_mode {
+ PresentationMode::Raw => {
+ let mut raw_array = self.env.create_array(row.len() as u32).ok()?;
+ for (idx, value) in row.get_values().enumerate() {
+ let js_value = to_js_value(&self.env, value);
+ raw_array.set(idx as u32, js_value).ok()?;
+ }
- let mut raw_array = self.env.create_array(row.len() as u32).ok()?;
- for (idx, value) in row.get_values().enumerate() {
- let js_value = to_js_value(&self.env, value);
- raw_array.set(idx as u32, js_value).ok()?;
+ Some(raw_array.coerce_to_object().ok()?.into_unknown())
}
+ PresentationMode::Pluck => {
+ let (_, value) = row.get_values().enumerate().next()?;
+ to_js_value(&self.env, value).ok()
+ }
+ PresentationMode::None => {
+ let mut js_row = self.env.create_object().ok()?;
+ for (idx, value) in row.get_values().enumerate() {
+ let key = stmt.get_column_name(idx);
+ let js_value = to_js_value(&self.env, value);
+ js_row.set_named_property(&key, js_value).ok()?;
+ }
- // TODO: fix this unwrap
- return Some(raw_array.coerce_to_object().unwrap());
+ Some(js_row.into_unknown())
+ }
}
-
- if self.pluck {
- assert!(!self.raw, "Cannot use pluck mode with raw mode");
-
- let (idx, value) = row.get_values().enumerate().next()?;
- let key = stmt.get_column_name(idx);
- let js_value = to_js_value(&self.env, value);
- js_row.set_named_property(&key, js_value).ok()?;
- return Some(js_row);
- }
-
- for (idx, value) in row.get_values().enumerate() {
- let key = stmt.get_column_name(idx);
- let js_value = to_js_value(&self.env, value);
- js_row.set_named_property(&key, js_value).ok()?;
- }
-
- Some(js_row)
}
limbo_core::StepResult::Done => None,
limbo_core::StepResult::IO => {
self.database.io.run_once().ok()?;
- None // clearly it's incorrect it should return to user
+ None // clearly it's incorrect, it should return to user
}
limbo_core::StepResult::Interrupt | limbo_core::StepResult::Busy => None,
}