use oxisql_core::{Connection, Value};
use oxisql_sqlite_compat::SqliteConnection;
struct TempDbPath {
path: std::path::PathBuf,
}
impl TempDbPath {
fn new(tag: &str) -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut path = std::env::temp_dir();
path.push(format!(
"oxisql_ofb_{}_{}_{}.db",
tag,
std::process::id(),
n
));
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(format!("{}-wal", path.display()));
Self { path }
}
fn as_str(&self) -> &str {
self.path.to_str().expect("temp db path is valid UTF-8")
}
}
impl Drop for TempDbPath {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
let _ = std::fs::remove_file(format!("{}-wal", self.path.display()));
}
}
async fn build_image_bytes(temp: &TempDbPath, big: &str) -> Vec<u8> {
{
let conn = SqliteConnection::open(temp.as_str())
.await
.expect("open temp db");
conn.execute(
"CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, body TEXT)",
&[],
)
.await
.expect("create table");
conn.execute("CREATE INDEX idx_name ON t (name)", &[])
.await
.expect("create index");
conn.execute(
"INSERT INTO t (id, name, body) VALUES ($1, $2, $3)",
&[&1i64, &"alice", &big],
)
.await
.expect("insert overflow row");
conn.execute(
"INSERT INTO t (id, name, body) VALUES ($1, $2, $3)",
&[&2i64, &"bob", &"short"],
)
.await
.expect("insert small row");
conn.execute("PRAGMA wal_checkpoint", &[])
.await
.expect("checkpoint");
}
std::fs::read(temp.as_str()).expect("read database file bytes")
}
#[tokio::test]
async fn test_open_from_bytes_matches_file_results() {
let temp = TempDbPath::new("match");
let big = "z".repeat(10_000); let bytes = build_image_bytes(&temp, &big).await;
let conn = SqliteConnection::open_from_bytes(&bytes)
.await
.expect("open_from_bytes");
let rows = conn.query("SELECT count(*) FROM t", &[]).await.unwrap();
assert_eq!(rows[0].get_by_index(0), Some(&Value::I64(2)));
let rows = conn
.query("SELECT body FROM t WHERE id = $1", &[&1i64])
.await
.unwrap();
assert_eq!(rows[0].get_by_index(0), Some(&Value::Text(big.clone())));
let rows = conn
.query("SELECT name FROM t ORDER BY name", &[])
.await
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].get_by_index(0), Some(&Value::Text("alice".into())));
assert_eq!(rows[1].get_by_index(0), Some(&Value::Text("bob".into())));
}
#[tokio::test]
async fn test_open_from_bytes_write_after_open() {
let temp = TempDbPath::new("write_after");
let big = "q".repeat(8_000);
let bytes = build_image_bytes(&temp, &big).await;
let conn = SqliteConnection::open_from_bytes(&bytes)
.await
.expect("open_from_bytes");
conn.execute(
"INSERT INTO t (id, name, body) VALUES ($1, $2, $3)",
&[&3i64, &"carol", &"new"],
)
.await
.expect("insert after open");
let rows = conn.query("SELECT count(*) FROM t", &[]).await.unwrap();
assert_eq!(rows[0].get_by_index(0), Some(&Value::I64(3)));
let reopened = SqliteConnection::open_from_bytes(&bytes)
.await
.expect("reopen original bytes");
let rows = reopened.query("SELECT count(*) FROM t", &[]).await.unwrap();
assert_eq!(
rows[0].get_by_index(0),
Some(&Value::I64(2)),
"a second open of the same bytes must not see the post-open insert"
);
}
#[tokio::test]
async fn test_open_from_bytes_empty_is_err() {
let err = SqliteConnection::open_from_bytes(&[]).await;
assert!(err.is_err(), "empty bytes must error, not panic");
}
#[tokio::test]
async fn test_open_from_bytes_garbage_is_err() {
let garbage = vec![0xABu8; 4096];
let err = SqliteConnection::open_from_bytes(&garbage).await;
assert!(err.is_err(), "garbage bytes must error, not panic");
}
#[tokio::test]
async fn test_open_from_bytes_truncated_is_err() {
let temp = TempDbPath::new("trunc");
let bytes = build_image_bytes(&temp, "small").await;
let truncated = &bytes[..50];
let err = SqliteConnection::open_from_bytes(truncated).await;
assert!(err.is_err(), "truncated header must error, not panic");
}