mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-26 04:24:21 +01:00
Merge 'bind/js: Add support for bind() method and reduce boilerplate' from Diego Reis
EDIT: This PR also adds support for the `pluck()` logic in all methods Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1582
This commit is contained in:
@@ -58,6 +58,25 @@ test("Test pragma", async (t) => {
|
||||
t.deepEqual(typeof db.pragma("cache_size", { simple: true }), "number");
|
||||
});
|
||||
|
||||
test("Test 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");
|
||||
|
||||
for (const row of stmt.iterate()) {
|
||||
t.truthy(row.name);
|
||||
t.true(typeof row.age === "number");
|
||||
}
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
db.bind("Bob");
|
||||
},
|
||||
{ instanceOf: Error },
|
||||
);
|
||||
});
|
||||
|
||||
const connect = async (path) => {
|
||||
const db = new Database(path);
|
||||
return [db];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import test from "ava";
|
||||
import fs from "fs";
|
||||
|
||||
import { Database } from "../wrapper.js";
|
||||
|
||||
@@ -66,12 +65,44 @@ test("Empty prepared statement should throw", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("Test pragma", async (t) => {
|
||||
test("Test pragma()", async (t) => {
|
||||
const [db] = await connect(":memory:");
|
||||
t.true(typeof db.pragma("cache_size")[0].cache_size === "number");
|
||||
t.true(typeof db.pragma("cache_size", { simple: true }) === "number");
|
||||
});
|
||||
|
||||
test("Statement binded with bind() shouldn't be binded again", 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");
|
||||
|
||||
for (const row of stmt.iterate()) {
|
||||
t.truthy(row.name);
|
||||
t.true(typeof row.age === "number");
|
||||
}
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
db.bind("Bob");
|
||||
},
|
||||
{ instanceOf: Error },
|
||||
);
|
||||
});
|
||||
|
||||
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.name);
|
||||
t.true(typeof row.age === "undefined");
|
||||
}
|
||||
});
|
||||
|
||||
const connect = async (path) => {
|
||||
const db = new Database(path);
|
||||
return [db];
|
||||
|
||||
@@ -170,8 +170,8 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add the (parent) 'database' property
|
||||
#[napi]
|
||||
#[derive(Clone)]
|
||||
pub struct Statement {
|
||||
// TODO: implement each property when core supports it
|
||||
// #[napi(able = false)]
|
||||
@@ -183,8 +183,9 @@ pub struct Statement {
|
||||
#[napi(writable = false)]
|
||||
pub source: String,
|
||||
|
||||
toggle: bool,
|
||||
database: Database,
|
||||
pluck: bool,
|
||||
binded: bool,
|
||||
inner: Rc<RefCell<limbo_core::Statement>>,
|
||||
}
|
||||
|
||||
@@ -195,21 +196,14 @@ impl Statement {
|
||||
inner: Rc::new(inner),
|
||||
database,
|
||||
source,
|
||||
toggle: false,
|
||||
pluck: false,
|
||||
binded: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
|
||||
if let Some(args) = args {
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
let value = from_js_value(elem)?;
|
||||
stmt.bind_at(NonZeroUsize::new(i + 1).unwrap(), value);
|
||||
}
|
||||
}
|
||||
let mut stmt = self.check_and_bind(args)?;
|
||||
|
||||
let step = stmt.step().map_err(into_napi_error)?;
|
||||
match step {
|
||||
@@ -220,6 +214,10 @@ impl Statement {
|
||||
let key = stmt.get_column_name(idx);
|
||||
let js_value = to_js_value(&env, value);
|
||||
obj.set_named_property(&key, js_value)?;
|
||||
|
||||
if self.pluck {
|
||||
return Ok(obj.into_unknown());
|
||||
}
|
||||
}
|
||||
Ok(obj.into_unknown())
|
||||
}
|
||||
@@ -234,14 +232,7 @@ impl Statement {
|
||||
// TODO: Return Info object (https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#runbindparameters---object)
|
||||
#[napi]
|
||||
pub fn run(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
if let Some(args) = args {
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
let value = from_js_value(elem)?;
|
||||
stmt.bind_at(NonZeroUsize::new(i + 1).unwrap(), value);
|
||||
}
|
||||
}
|
||||
let stmt = self.check_and_bind(args)?;
|
||||
|
||||
self.internal_all(env, stmt)
|
||||
}
|
||||
@@ -252,32 +243,19 @@ impl Statement {
|
||||
env: Env,
|
||||
args: Option<Vec<JsUnknown>>,
|
||||
) -> napi::Result<IteratorStatement> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
if let Some(args) = args {
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
let value = from_js_value(elem)?;
|
||||
stmt.bind_at(NonZeroUsize::new(i + 1).unwrap(), value);
|
||||
}
|
||||
}
|
||||
self.check_and_bind(args)?;
|
||||
|
||||
Ok(IteratorStatement {
|
||||
stmt: Rc::clone(&self.inner),
|
||||
database: self.database.clone(),
|
||||
env,
|
||||
plucked: self.pluck,
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn all(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
if let Some(args) = args {
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
let value = from_js_value(elem)?;
|
||||
stmt.bind_at(NonZeroUsize::new(i + 1).unwrap(), value);
|
||||
}
|
||||
}
|
||||
let stmt = self.check_and_bind(args)?;
|
||||
|
||||
self.internal_all(env, stmt)
|
||||
}
|
||||
@@ -298,6 +276,10 @@ impl Statement {
|
||||
let key = stmt.get_column_name(idx);
|
||||
let js_value = to_js_value(&env, value);
|
||||
obj.set_named_property(&key, js_value)?;
|
||||
|
||||
if self.pluck {
|
||||
break;
|
||||
}
|
||||
}
|
||||
results.set_element(index, obj)?;
|
||||
index += 1;
|
||||
@@ -321,29 +303,60 @@ impl Statement {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn pluck(&mut self, toggle: Option<bool>) {
|
||||
if let Some(false) = toggle {
|
||||
self.toggle = false;
|
||||
pub fn pluck(&mut self, pluck: Option<bool>) {
|
||||
if let Some(false) = pluck {
|
||||
self.pluck = false;
|
||||
}
|
||||
|
||||
self.toggle = true;
|
||||
self.pluck = true;
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn expand() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn raw() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn columns() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn bind() {
|
||||
todo!()
|
||||
pub fn bind(&mut self, args: Option<Vec<JsUnknown>>) -> napi::Result<Self> {
|
||||
self.check_and_bind(args)?;
|
||||
self.binded = true;
|
||||
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
/// Check if the Statement is already binded by the `bind()` method
|
||||
/// and bind values do variables. The expected type for args is `Option<Vec<JsUnknown>>`
|
||||
fn check_and_bind(
|
||||
&self,
|
||||
args: Option<Vec<JsUnknown>>,
|
||||
) -> napi::Result<RefMut<'_, limbo_core::Statement>> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
if let Some(args) = args {
|
||||
if self.binded {
|
||||
return Err(napi::Error::new(
|
||||
napi::Status::InvalidArg,
|
||||
"This statement already has bound parameters",
|
||||
));
|
||||
}
|
||||
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
let value = from_js_value(elem)?;
|
||||
stmt.bind_at(NonZeroUsize::new(i + 1).unwrap(), value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +365,7 @@ pub struct IteratorStatement {
|
||||
stmt: Rc<RefCell<limbo_core::Statement>>,
|
||||
database: Database,
|
||||
env: Env,
|
||||
plucked: bool,
|
||||
}
|
||||
|
||||
impl Generator for IteratorStatement {
|
||||
@@ -373,6 +387,10 @@ impl Generator for IteratorStatement {
|
||||
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()?;
|
||||
|
||||
if self.plucked {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some(js_row)
|
||||
|
||||
@@ -224,6 +224,16 @@ class Statement {
|
||||
columns() {
|
||||
return this.stmt.columns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given parameters to the statement _permanently_
|
||||
*
|
||||
* @param bindParameters - The bind parameters for binding the statement.
|
||||
* @returns this - Statement with binded parameters
|
||||
*/
|
||||
bind(...bindParameters) {
|
||||
return this.stmt.bind(bindParameters.flat());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Database = Database;
|
||||
|
||||
Reference in New Issue
Block a user