Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
156 lines
5 KiB
Rust
156 lines
5 KiB
Rust
use makepad_sqlite::{
|
|
Connection, FileStoreSet, MemoryStoreSet, PageStoreSet, StoreKind, StoreOpenOptions, Value,
|
|
};
|
|
use std::sync::Arc;
|
|
|
|
fn exercise_sql(stores: Arc<dyn PageStoreSet>) {
|
|
let mut db = Connection::open_with(stores).unwrap();
|
|
db.execute(
|
|
"CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT NOT NULL, payload BLOB)",
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
db.execute("CREATE INDEX t_by_name ON t(name)", &[]).unwrap();
|
|
db.execute(
|
|
"INSERT INTO t(name, payload) VALUES ('one', x'0102'), ('two', x'03')",
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
let rows = db
|
|
.query("SELECT id, name, length(payload) FROM t ORDER BY id", &[])
|
|
.unwrap();
|
|
assert_eq!(rows.rows.len(), 2);
|
|
assert_eq!(rows.rows[1][1].as_text(), Some("two"));
|
|
assert_eq!(rows.rows[1][2].as_integer(), Some(1));
|
|
|
|
db.execute("BEGIN IMMEDIATE", &[]).unwrap();
|
|
db.execute("DELETE FROM t", &[]).unwrap();
|
|
assert_eq!(
|
|
db.query("SELECT COUNT(*) FROM t", &[]).unwrap().rows[0][0].as_integer(),
|
|
Some(0)
|
|
);
|
|
db.execute("ROLLBACK", &[]).unwrap();
|
|
assert_eq!(
|
|
db.query("SELECT COUNT(*) FROM t", &[]).unwrap().rows[0][0].as_integer(),
|
|
Some(2)
|
|
);
|
|
assert_eq!(
|
|
db.query("PRAGMA quick_check", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("ok")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn backend_agnostic_sql_runs_on_file_and_memory() {
|
|
let mut path = std::env::temp_dir();
|
|
path.push(format!(
|
|
"makepad-sqlite-page-store-{}-{}.db",
|
|
std::process::id(),
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos()
|
|
));
|
|
exercise_sql(Arc::new(FileStoreSet::new(&path)));
|
|
exercise_sql(Arc::new(MemoryStoreSet::new()));
|
|
let _ = std::fs::remove_file(path);
|
|
}
|
|
|
|
#[test]
|
|
fn memory_rollback_journal_is_created_and_removed() {
|
|
let stores = MemoryStoreSet::new();
|
|
let mut db = Connection::open_with(stores.clone()).unwrap();
|
|
assert_eq!(
|
|
db.query("PRAGMA journal_mode", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("delete")
|
|
);
|
|
db.execute("CREATE TABLE t(v TEXT)", &[]).unwrap();
|
|
assert!(stores
|
|
.open(StoreKind::Journal, StoreOpenOptions::READ_ONLY)
|
|
.unwrap()
|
|
.is_none());
|
|
|
|
db.execute("BEGIN IMMEDIATE", &[]).unwrap();
|
|
db.execute("INSERT INTO t VALUES ('uncommitted')", &[])
|
|
.unwrap();
|
|
let journal = stores
|
|
.open(StoreKind::Journal, StoreOpenOptions::READ_ONLY)
|
|
.unwrap()
|
|
.expect("rollback journal while transaction is open");
|
|
assert!(journal.len().unwrap() >= 512);
|
|
db.execute("ROLLBACK", &[]).unwrap();
|
|
assert!(stores
|
|
.open(StoreKind::Journal, StoreOpenOptions::READ_ONLY)
|
|
.unwrap()
|
|
.is_none());
|
|
assert_eq!(
|
|
db.query("SELECT COUNT(*) FROM t", &[]).unwrap().rows[0][0].as_integer(),
|
|
Some(0)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn memory_wal_appends_checkpoints_and_converts_back() {
|
|
let stores = MemoryStoreSet::new();
|
|
let mut db = Connection::open_with(stores.clone()).unwrap();
|
|
db.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)", &[])
|
|
.unwrap();
|
|
assert_eq!(
|
|
db.query("PRAGMA journal_mode=WAL", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("wal")
|
|
);
|
|
db.execute("INSERT INTO t(v) VALUES ('in-wal')", &[])
|
|
.unwrap();
|
|
assert!(db.pager().wal_frames() > 0);
|
|
let wal = stores
|
|
.open(StoreKind::Wal, StoreOpenOptions::READ_ONLY)
|
|
.unwrap()
|
|
.expect("WAL store");
|
|
assert!(wal.len().unwrap() > 32);
|
|
assert_eq!(
|
|
db.query("SELECT v FROM t", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("in-wal")
|
|
);
|
|
drop(db);
|
|
let mut db = Connection::open_with(stores.clone()).unwrap();
|
|
assert_eq!(
|
|
db.query("SELECT v FROM t", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("in-wal"),
|
|
"a reopened memory connection did not recover its WAL"
|
|
);
|
|
db.query("PRAGMA wal_checkpoint", &[]).unwrap();
|
|
assert_eq!(db.pager().wal_frames(), 0);
|
|
assert_eq!(
|
|
db.query("PRAGMA journal_mode=DELETE", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("delete")
|
|
);
|
|
assert!(stores
|
|
.open(StoreKind::Wal, StoreOpenOptions::READ_ONLY)
|
|
.unwrap()
|
|
.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn memory_quick_check_after_many_pages() {
|
|
let mut db = Connection::open_memory().unwrap();
|
|
db.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, k TEXT, payload BLOB)", &[])
|
|
.unwrap();
|
|
db.execute("CREATE INDEX t_by_k ON t(k)", &[]).unwrap();
|
|
db.execute("BEGIN IMMEDIATE", &[]).unwrap();
|
|
for i in 0..400i64 {
|
|
db.execute(
|
|
"INSERT INTO t(k, payload) VALUES (?1, ?2)",
|
|
&[
|
|
Value::text(format!("key-{:04}", i % 73)),
|
|
Value::Blob(vec![(i % 251) as u8; 6000]),
|
|
],
|
|
)
|
|
.unwrap();
|
|
}
|
|
db.execute("COMMIT", &[]).unwrap();
|
|
assert!(db.pager().page_count() > 400);
|
|
assert_eq!(
|
|
db.query("PRAGMA quick_check", &[]).unwrap().rows[0][0].as_text(),
|
|
Some("ok")
|
|
);
|
|
}
|