mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
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:
commit
bf26b8913f
6 changed files with 374 additions and 159 deletions
36
bindings/javascript/README.md
Normal file
36
bindings/javascript/README.md
Normal 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)
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
31
bindings/javascript/docs/CONTRIBUTING.md
Normal file
31
bindings/javascript/docs/CONTRIBUTING.md
Normal 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;
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue