Merge 'bindings/javascript: Refactor presentation mode and enhance test suite' from Diego Reis

Throughout the cleaning I discovered that the current pluck mode is
broken, so I took the lead and also fixed it.
EDIT: Address comments on #1620 by improving our documentation

Closes #1663
This commit is contained in:
Jussi Saurio 2025-06-09 08:22:08 +03:00
commit bf26b8913f
6 changed files with 374 additions and 159 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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);

View file

@ -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 | <code>function</code> | 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 | <code>string</code> | Pragma to be executed |
| options | <code>object</code> | 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 | <code>string</code> | The path to the extention to be loaded. |
### exec(sql) ⇒ this
@ -78,7 +88,7 @@ Executes a SQL statement.
| ------ | ------------------- | ------------------------------------ |
| sql | <code>string</code> | 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 | <code>array of objects</code> | 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 | <code>array of objects</code> | 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 | <code>array of objects</code> | 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 | <code>boolean</code> | 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 | <code>array of objects</code> | 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.

View file

@ -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;

View file

@ -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<RefCell<limbo_core::Statement>>,
}
@ -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<Vec<JsUnknown>>,
) -> napi::Result<IteratorStatement> {
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<bool>) {
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<bool>) {
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<RefCell<limbo_core::Statement>>,
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,
}