diff --git a/bindings/javascript/__test__/better-sqlite3.spec.mjs b/bindings/javascript/__test__/better-sqlite3.spec.mjs index 38b5e7b00..247eeda33 100644 --- a/bindings/javascript/__test__/better-sqlite3.spec.mjs +++ b/bindings/javascript/__test__/better-sqlite3.spec.mjs @@ -66,6 +66,7 @@ test("Test bind()", async (t) => { 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); diff --git a/bindings/javascript/__test__/limbo.spec.mjs b/bindings/javascript/__test__/limbo.spec.mjs index be3326137..df892398f 100644 --- a/bindings/javascript/__test__/limbo.spec.mjs +++ b/bindings/javascript/__test__/limbo.spec.mjs @@ -98,7 +98,7 @@ 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").pluck(); + let stmt = db.prepare("SELECT * FROM users").raw().pluck(); for (const row of stmt.iterate()) { t.truthy(row.name); @@ -106,6 +106,41 @@ test("Test pluck(): Rows should only have the values of the first column", async } }); + +test("Test raw()", 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(); + + 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("Test exec()", async (t) => { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/bindings/javascript/src/lib.rs b/bindings/javascript/src/lib.rs index e14c7ea6a..f587d6ccb 100644 --- a/bindings/javascript/src/lib.rs +++ b/bindings/javascript/src/lib.rs @@ -216,6 +216,7 @@ pub struct Statement { pub source: String, database: Database, + raw: bool, pluck: bool, binded: bool, inner: Rc>, @@ -230,6 +231,7 @@ impl Statement { source, pluck: false, binded: false, + raw: false, } } @@ -241,16 +243,43 @@ impl Statement { match step { limbo_core::StepResult::Row => { let row = stmt.row().unwrap(); + + if self.raw { + assert!(!self.pluck, "Cannot use raw mode with pluck mode"); + + 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)?; + } + + return Ok(raw_obj.coerce_to_object()?.into_unknown()); + } + let mut obj = env.create_object()?; - for (idx, value) in row.get_values().enumerate() { + 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)?; - if self.pluck { - return Ok(obj.into_unknown()); - } + 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()), @@ -276,12 +305,20 @@ 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, - plucked: self.pluck, + pluck: self.pluck, + raw: self.raw, }) } @@ -304,14 +341,40 @@ impl Statement { 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)?; + } + 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)?; - - if self.pluck { - break; - } } results.set_element(index, obj)?; index += 1; @@ -340,6 +403,7 @@ impl Statement { self.pluck = false; } + self.raw = false; self.pluck = true; } @@ -349,8 +413,13 @@ impl Statement { } #[napi] - pub fn raw() { - todo!() + pub fn raw(&mut self, raw: Option) { + if let Some(false) = raw { + self.raw = false; + } + + self.pluck = false; + self.raw = true; } #[napi] @@ -397,7 +466,8 @@ pub struct IteratorStatement { stmt: Rc>, database: Database, env: Env, - plucked: bool, + pluck: bool, + raw: bool, } impl Generator for IteratorStatement { @@ -415,14 +485,33 @@ impl Generator for IteratorStatement { 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"); + + 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()?; + } + + // TODO: fix this unwrap + return Some(raw_array.coerce_to_object().unwrap()); + } + + 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()?; - - if self.plucked { - break; - } } Some(js_row)