use spg_embedded::{Database, Value};
use std::path::PathBuf;
struct Scratch {
path: PathBuf,
}
impl Scratch {
fn new(label: &str) -> Self {
let mut p = std::env::temp_dir();
let nanos: u64 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
p.push(format!("spg-embedded-prepare-{label}-{nanos}"));
std::fs::create_dir_all(&p).unwrap();
Self { path: p }
}
fn child(&self, name: &str) -> PathBuf {
self.path.join(name)
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.path);
}
}
#[test]
fn prepare_then_bind_query() {
let mut db = Database::open_in_memory();
db.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL)")
.unwrap();
db.execute("INSERT INTO users VALUES (1, 'alice')").unwrap();
db.execute("INSERT INTO users VALUES (2, 'bob')").unwrap();
db.execute("INSERT INTO users VALUES (3, 'carol')").unwrap();
let stmt = db.prepare("SELECT name FROM users WHERE id = $1").unwrap();
let rows = db.query_prepared(&stmt, &[Value::Int(2)]).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][0], Value::Text("bob".into()));
let rows = db.query_prepared(&stmt, &[Value::Int(1)]).unwrap();
assert_eq!(rows[0][0], Value::Text("alice".into()));
let rows = db.query_prepared(&stmt, &[Value::Int(3)]).unwrap();
assert_eq!(rows[0][0], Value::Text("carol".into()));
}
#[test]
fn prepare_then_bind_dml() {
let mut db = Database::open_in_memory();
db.execute("CREATE TABLE items (id INT NOT NULL, qty INT NOT NULL)")
.unwrap();
let insert = db
.prepare("INSERT INTO items VALUES ($1, $2)")
.unwrap();
db.execute_prepared(&insert, &[Value::Int(1), Value::Int(10)])
.unwrap();
db.execute_prepared(&insert, &[Value::Int(2), Value::Int(20)])
.unwrap();
db.execute_prepared(&insert, &[Value::Int(3), Value::Int(30)])
.unwrap();
let rows = db.query("SELECT id, qty FROM items ORDER BY id").unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0], vec![Value::Int(1), Value::Int(10)]);
assert_eq!(rows[2], vec![Value::Int(3), Value::Int(30)]);
}
#[test]
fn prepare_multi_param_mixed_types() {
let mut db = Database::open_in_memory();
db.execute(
"CREATE TABLE events (id INT NOT NULL, name TEXT NOT NULL, \
active BOOLEAN NOT NULL, ts TIMESTAMPTZ NOT NULL)",
)
.unwrap();
let insert = db
.prepare("INSERT INTO events VALUES ($1, $2, $3, $4)")
.unwrap();
db.execute_prepared(
&insert,
&[
Value::Int(1),
Value::Text("signin".into()),
Value::Bool(true),
Value::Text("2026-06-06 12:00:00+00".into()),
],
)
.unwrap();
let select = db
.prepare("SELECT id, name, active FROM events WHERE active = $1")
.unwrap();
let rows = db
.query_prepared(&select, &[Value::Bool(true)])
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][0], Value::Int(1));
assert_eq!(rows[0][1], Value::Text("signin".into()));
assert_eq!(rows[0][2], Value::Bool(true));
}
#[test]
fn prepare_query_on_dml_errors() {
let mut db = Database::open_in_memory();
db.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
let dml = db.prepare("INSERT INTO t VALUES ($1)").unwrap();
let err = db.query_prepared(&dml, &[Value::Int(1)]).unwrap_err();
assert!(err.to_string().contains("SELECT"));
}
#[test]
fn prepare_arity_mismatch_errors() {
let mut db = Database::open_in_memory();
db.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
db.execute("INSERT INTO t VALUES (1)").unwrap();
let stmt = db.prepare("SELECT id FROM t WHERE id = $1").unwrap();
let err = db.query_prepared(&stmt, &[]).unwrap_err();
let msg = err.to_string().to_lowercase();
assert!(
msg.contains("parameter $1") || msg.contains("placeholder"),
"expected parameter-arity error, got: {err}"
);
}
#[test]
fn prepared_dml_persists_via_wal() {
let dir = Scratch::new("wal-smoke");
let path = dir.child("db");
{
let mut db = Database::open_path(&path).unwrap();
db.execute("CREATE TABLE kv (k INT NOT NULL, v TEXT NOT NULL)")
.unwrap();
let stmt = db.prepare("INSERT INTO kv VALUES ($1, $2)").unwrap();
db.execute_prepared(&stmt, &[Value::Int(1), Value::Text("one".into())])
.unwrap();
db.execute_prepared(&stmt, &[Value::Int(2), Value::Text("two".into())])
.unwrap();
}
let mut db = Database::open_path(&path).unwrap();
let rows = db.query("SELECT k, v FROM kv ORDER BY k").unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0], vec![Value::Int(1), Value::Text("one".into())]);
assert_eq!(rows[1], vec![Value::Int(2), Value::Text("two".into())]);
}