#![cfg(feature = "embedded")]
use std::path::{Path, PathBuf};
use reddb_client::embedded::EmbeddedClient;
use reddb_client::JsonValue;
use reddb_server::storage::EmbeddedRdbArtifact;
struct TempDbPath {
_dir: tempfile::TempDir,
path: PathBuf,
}
impl std::ops::Deref for TempDbPath {
type Target = Path;
fn deref(&self) -> &Path {
&self.path
}
}
fn unique_db_path(label: &str) -> TempDbPath {
let dir = tempfile::Builder::new()
.prefix(&format!("reddb-test-bulk-{label}-"))
.tempdir()
.expect("temp dir");
let path = dir.path().join(format!("reddb-bulk-{label}.rdb"));
TempDbPath { _dir: dir, path }
}
fn wal_stats(data_path: &Path) -> (usize, u64) {
let open = EmbeddedRdbArtifact::open(data_path).expect("open rdb artifact");
let payloads = EmbeddedRdbArtifact::read_wal_payloads(&open).expect("read wal payloads");
let bytes = EmbeddedRdbArtifact::wal_payloads_encoded_len(&payloads).expect("wal encoded len");
(payloads.len(), bytes)
}
fn rows(n: usize) -> Vec<JsonValue> {
(0..n)
.map(|i| {
JsonValue::object([
("name", JsonValue::string(format!("user_{i}"))),
("age", JsonValue::number(20.0 + i as f64)),
])
})
.collect()
}
#[test]
fn bulk_insert_emits_one_wal_record_per_batch() {
const N: usize = 50;
let bulk_path = unique_db_path("bulk");
let bulk_count = {
let db = EmbeddedClient::open(bulk_path.to_path_buf()).expect("open bulk db");
let inserted = db.bulk_insert("users", &rows(N)).expect("bulk insert");
assert_eq!(
inserted.affected, N as u64,
"bulk_insert returned wrong count"
);
assert_eq!(
inserted.ids.len(),
N,
"bulk_insert returned wrong ids count"
);
let after = wal_stats(&bulk_path).0;
drop(db);
after
};
let perrow_path = unique_db_path("perrow");
let perrow_count = {
let db = EmbeddedClient::open(perrow_path.to_path_buf()).expect("open perrow db");
for i in 0..N {
let sql = format!(
"INSERT INTO users (name, age) VALUES ('user_{i}', {})",
20 + i
);
db.query(&sql).expect("per-row insert");
}
let after = wal_stats(&perrow_path).0;
drop(db);
after
};
eprintln!(
"WAL records for {N} rows: bulk_insert={bulk_count}, per-row={perrow_count} (delta {})",
perrow_count.saturating_sub(bulk_count)
);
assert!(
perrow_count >= bulk_count + N / 2,
"expected per-row WAL ({perrow_count} records) to dwarf batch WAL ({bulk_count}) by ~N — bulk_insert likely regressed back to a per-row loop"
);
}
#[test]
fn bulk_insert_round_trip() {
let path = unique_db_path("round-trip");
let db = EmbeddedClient::open(path.to_path_buf()).expect("open db");
let inserted = db
.bulk_insert(
"items",
&[
JsonValue::object([
("sku", JsonValue::string("A1")),
("qty", JsonValue::number(3.0)),
]),
JsonValue::object([
("sku", JsonValue::string("B2")),
("qty", JsonValue::number(7.0)),
]),
JsonValue::object([
("sku", JsonValue::string("C3")),
("qty", JsonValue::number(11.0)),
]),
],
)
.expect("bulk insert");
assert_eq!(inserted.affected, 3);
assert_eq!(inserted.ids.len(), 3);
let result = db
.query("SELECT sku, qty FROM items")
.expect("select after bulk");
assert_eq!(result.rows.len(), 3, "expected 3 rows back from select");
drop(db);
}
#[test]
fn bulk_insert_heterogeneous_payloads_still_work() {
let path = unique_db_path("hetero");
let db = EmbeddedClient::open(path.to_path_buf()).expect("open db");
let inserted = db
.bulk_insert(
"events",
&[
JsonValue::object([
("kind", JsonValue::string("login")),
("user", JsonValue::string("alice")),
]),
JsonValue::object([("kind", JsonValue::string("logout"))]),
],
)
.expect("bulk insert hetero");
assert_eq!(inserted.affected, 2);
assert_eq!(inserted.ids.len(), 2);
let result = db.query("SELECT kind FROM events").expect("select hetero");
assert_eq!(result.rows.len(), 2);
drop(db);
}
#[test]
fn bulk_insert_empty_is_noop() {
let path = unique_db_path("empty");
let db = EmbeddedClient::open(path.to_path_buf()).expect("open db");
let inserted = db.bulk_insert("anything", &[]).expect("empty bulk");
assert_eq!(inserted.affected, 0);
assert!(inserted.ids.is_empty());
drop(db);
}
#[test]
fn insert_emits_one_wal_record_per_call() {
let insert_path = unique_db_path("insert-one");
let (insert_count, insert_bytes) = {
let db = EmbeddedClient::open(insert_path.to_path_buf()).expect("open insert db");
let res = db
.insert(
"users",
&JsonValue::object([
("name", JsonValue::string("solo".to_string())),
("age", JsonValue::number(42.0)),
]),
)
.expect("single insert");
assert_eq!(res.affected, 1, "insert returned wrong affected count");
let after = wal_stats(&insert_path);
drop(db);
after
};
let bulk_path = unique_db_path("bulk-one");
let (bulk_count, bulk_bytes) = {
let db = EmbeddedClient::open(bulk_path.to_path_buf()).expect("open bulk db");
let inserted = db
.bulk_insert(
"users",
&[JsonValue::object([
("name", JsonValue::string("solo".to_string())),
("age", JsonValue::number(42.0)),
])],
)
.expect("bulk insert one");
assert_eq!(inserted.affected, 1);
assert_eq!(inserted.ids.len(), 1);
let after = wal_stats(&bulk_path);
drop(db);
after
};
eprintln!(
"WAL for 1 row: insert=({insert_count} records, {insert_bytes} bytes), bulk_insert(1)=({bulk_count} records, {bulk_bytes} bytes)"
);
assert_eq!(
insert_count, bulk_count,
"insert WAL record count ({insert_count}) should match 1-row bulk_insert ({bulk_count})"
);
assert_eq!(
insert_bytes, bulk_bytes,
"insert WAL bytes ({insert_bytes}) should match 1-row bulk_insert ({bulk_bytes}) — insert likely regressed back to the SQL round-trip"
);
}
#[test]
fn insert_round_trip() {
let path = unique_db_path("insert-round-trip");
let db = EmbeddedClient::open(path.to_path_buf()).expect("open db");
let res = db
.insert(
"items",
&JsonValue::object([
("sku", JsonValue::string("X9".to_string())),
("qty", JsonValue::number(13.0)),
]),
)
.expect("single insert");
assert_eq!(res.affected, 1);
let rid = res.rid.expect("insert returns assigned rid");
let result = db
.query("SELECT rid, sku, qty FROM items")
.expect("select after insert");
assert_eq!(result.rows.len(), 1, "expected 1 row back from select");
let returned_rid = result.rows[0]
.iter()
.find_map(|(name, value)| (name == "rid").then_some(value.to_string()))
.expect("select returns rid");
assert_eq!(rid, returned_rid);
drop(db);
}