diff --git a/bindings/javascript/__test__/better-sqlite3.spec.mjs b/bindings/javascript/__test__/better-sqlite3.spec.mjs index b0a247cfd..0af24075e 100644 --- a/bindings/javascript/__test__/better-sqlite3.spec.mjs +++ b/bindings/javascript/__test__/better-sqlite3.spec.mjs @@ -31,6 +31,13 @@ test("Statement.run() returns correct result object", async (t) => { t.deepEqual(rows, { changes: 1, lastInsertRowid: 1 }); }); +test("Empty prepared statement should throw", async (t) => { + const [db] = await connect(":memory:"); + t.throws(() => { + db.prepare(""); + }, { instanceOf: Error }); +}); + const connect = async (path) => { const db = new Database(path); return [db]; diff --git a/bindings/javascript/__test__/limbo.spec.mjs b/bindings/javascript/__test__/limbo.spec.mjs index ee24139fb..617898d78 100644 --- a/bindings/javascript/__test__/limbo.spec.mjs +++ b/bindings/javascript/__test__/limbo.spec.mjs @@ -1,27 +1,26 @@ import test from "ava"; -import { Database } from "../index.js"; +import { Database } from "../index.js"; test("Open in-memory database", async (t) => { - const [db] = await connect(":memory:"); - t.is(db.memory, true); + const [db] = await connect(":memory:"); + t.is(db.memory, true); }); - test("Statement.get() returns data", async (t) => { - const [db] = await connect(":memory:"); - const stmt = db.prepare("SELECT 1"); - const result = stmt.get(); - t.is(result["1"], 1); - const result2 = stmt.get(); - t.is(result2["1"], 1); + const [db] = await connect(":memory:"); + const stmt = db.prepare("SELECT 1"); + const result = stmt.get(); + t.is(result["1"], 1); + const result2 = stmt.get(); + t.is(result2["1"], 1); }); test("Statement.get() returns null when no data", async (t) => { - const [db] = await connect(":memory:"); - const stmt = db.prepare("SELECT 1 WHERE 1 = 2"); - const result = stmt.get(); - t.is(result, undefined); + const [db] = await connect(":memory:"); + const stmt = db.prepare("SELECT 1 WHERE 1 = 2"); + const result = stmt.get(); + t.is(result, undefined); }); // run() isn't 100% compatible with better-sqlite3 @@ -30,11 +29,21 @@ test("Statement.run() returns correct result object", async (t) => { const [db] = await connect(":memory:"); db.prepare("CREATE TABLE users (name TEXT)").run(); db.prepare("INSERT INTO users (name) VALUES (?)").run(["Alice"]); - let rows = db.prepare("SELECT * FROM users").all() + let rows = db.prepare("SELECT * FROM users").all(); t.deepEqual(rows, [{ name: "Alice" }]); }); +test("Empty prepared statement should throw", async (t) => { + const [db] = await connect(":memory:"); + t.throws( + () => { + db.prepare(""); + }, + { instanceOf: Error }, + ); +}); + const connect = async (path) => { - const db = new Database(path); - return [db]; + const db = new Database(path); + return [db]; }; diff --git a/core/lib.rs b/core/lib.rs index f36072e3a..965023022 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -325,37 +325,40 @@ pub struct Connection { impl Connection { pub fn prepare(self: &Rc, sql: impl AsRef) -> Result { + if sql.as_ref().is_empty() { + return Err(LimboError::InvalidArgument( + "The supplied SQL string contains no statements".to_string(), + )); + } + let sql = sql.as_ref(); tracing::trace!("Preparing: {}", sql); let mut parser = Parser::new(sql.as_bytes()); let cmd = parser.next()?; let syms = self.syms.borrow(); - if let Some(cmd) = cmd { - match cmd { - Cmd::Stmt(stmt) => { - let program = Rc::new(translate::translate( - self.schema - .try_read() - .ok_or(LimboError::SchemaLocked)? - .deref(), - stmt, - self.header.clone(), - self.pager.clone(), - Rc::downgrade(self), - &syms, - QueryMode::Normal, - )?); - Ok(Statement::new( - program, - self._db.mv_store.clone(), - self.pager.clone(), - )) - } - Cmd::Explain(_stmt) => todo!(), - Cmd::ExplainQueryPlan(_stmt) => todo!(), + let cmd = cmd.expect("Successful parse on nonempty input string should produce a command"); + match cmd { + Cmd::Stmt(stmt) => { + let program = Rc::new(translate::translate( + self.schema + .try_read() + .ok_or(LimboError::SchemaLocked)? + .deref(), + stmt, + self.header.clone(), + self.pager.clone(), + Rc::downgrade(self), + &syms, + QueryMode::Normal, + )?); + Ok(Statement::new( + program, + self._db.mv_store.clone(), + self.pager.clone(), + )) } - } else { - todo!() + Cmd::Explain(_stmt) => todo!(), + Cmd::ExplainQueryPlan(_stmt) => todo!(), } }