use quantydb_sqlite::{
open_file, FileSource, Reader, SliceSource, SqliteError, SqliteValue, WalSource,
};
fn data_path(name: &str) -> String {
format!("{}/tests/data/{name}", env!("CARGO_MANIFEST_DIR"))
}
fn rows_of(reader: &Reader<impl quantydb_sqlite::Source>, table: &str) -> Vec<(i64, String)> {
let schema = reader.schema().unwrap();
let object = schema
.object(table)
.unwrap_or_else(|| panic!("{table} is not in the schema"));
reader
.rows(object.root_page.unwrap())
.unwrap()
.map(|row| {
let row = row.unwrap();
let text = match &row.values[1] {
SqliteValue::Text(t) => t.clone(),
other => panic!("expected text, got {other:?}"),
};
(row.rowid.unwrap(), text)
})
.collect()
}
#[test]
fn the_log_supplies_what_the_main_file_is_missing() {
let reader = open_file(data_path("wal_mode.sqlite")).unwrap();
assert_eq!(reader.page_count(), 11);
let t = rows_of(&reader, "t");
assert_eq!(t.len(), 20, "t holds 20 rows");
assert_eq!(t[0], (1, "rewritten-2".to_string()));
assert_eq!(t[1], (2, "original-02".to_string()));
assert!(
!t.iter().any(|(id, _)| *id == 999),
"a row from the rolled back transaction was returned"
);
assert!(
!t.iter().any(|(_, v)| v.contains("never-committed")),
"a value from the rolled back transaction was returned"
);
let grown = rows_of(&reader, "grown");
assert_eq!(grown.len(), 200);
assert_eq!(grown[0], (1, "wal-only-001".to_string()));
assert_eq!(grown[199], (200, "wal-only-200".to_string()));
}
#[test]
fn without_the_log_the_same_file_is_refused() {
let bytes = std::fs::read(data_path("wal_mode.sqlite")).unwrap();
let err = match Reader::open(SliceSource::new(&bytes)) {
Ok(_) => panic!("a wal mode database was read without its log"),
Err(e) => e,
};
assert!(matches!(err, SqliteError::Unsupported(_)));
let message = err.to_string();
assert!(message.contains("wal"), "message was: {message}");
assert!(
message.contains("open_file") || message.contains("checkpoint"),
"the message should say what to do instead: {message}"
);
}
#[test]
fn a_checkpointed_database_is_not_refused() {
let reader = open_file(data_path("wal_checkpointed.sqlite")).unwrap();
assert!(reader.header().wal_mode, "the flag is set");
assert_eq!(rows_of(&reader, "t").len(), 50);
let reader = Reader::open(FileSource::open(data_path("wal_checkpointed.sqlite")).unwrap())
.expect("a checkpointed database opens without ceremony");
assert_eq!(rows_of(&reader, "t").len(), 50);
}
#[test]
fn an_absent_log_changes_nothing() {
let with_none: WalSource<FileSource, FileSource> =
WalSource::new(FileSource::open(data_path("chinook.sqlite")).unwrap(), None).unwrap();
assert!(!with_none.has_log());
let through_adapter = Reader::open(with_none).unwrap();
let plain = Reader::open(FileSource::open(data_path("chinook.sqlite")).unwrap()).unwrap();
assert_eq!(through_adapter.page_count(), plain.page_count());
assert_eq!(
rows_of(&through_adapter, "Album").len(),
rows_of(&plain, "Album").len()
);
}
#[test]
fn an_empty_log_is_the_same_as_none() {
let empty = std::fs::read(data_path("wal_checkpointed.sqlite")).unwrap();
let source: WalSource<SliceSource<'_>, SliceSource<'_>> =
WalSource::new(SliceSource::new(&empty), Some(SliceSource::new(&[]))).unwrap();
assert!(!source.has_log(), "an empty log holds no committed frames");
assert_eq!(Reader::open(source).unwrap().page_count(), 4);
}
#[test]
fn a_log_written_for_another_page_size_is_refused() {
let main = std::fs::read(data_path("wal_mode.sqlite")).unwrap();
let mut log = std::fs::read(data_path("wal_mode.sqlite-wal")).unwrap();
log[8..12].copy_from_slice(&1024u32.to_be_bytes());
let (mut s0, mut s1) = (0u32, 0u32);
for chunk in log[..24].as_chunks::<8>().0 {
let x = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
let y = u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
s0 = s0.wrapping_add(x).wrapping_add(s1);
s1 = s1.wrapping_add(y).wrapping_add(s0);
}
log[24..28].copy_from_slice(&s0.to_be_bytes());
log[28..32].copy_from_slice(&s1.to_be_bytes());
let result: Result<WalSource<SliceSource<'_>, SliceSource<'_>>, SqliteError> =
WalSource::new(SliceSource::new(&main), Some(SliceSource::new(&log)));
match result {
Ok(_) => panic!("a log with the wrong page size was accepted"),
Err(e) => assert!(e.to_string().contains("page"), "message was: {e}"),
}
}
#[test]
fn a_read_spanning_two_pages_gets_each_half_from_the_right_place() {
let main = std::fs::read(data_path("wal_mode.sqlite")).unwrap();
let log = std::fs::read(data_path("wal_mode.sqlite-wal")).unwrap();
let source: WalSource<SliceSource<'_>, SliceSource<'_>> =
WalSource::new(SliceSource::new(&main), Some(SliceSource::new(&log))).unwrap();
use quantydb_sqlite::Source;
let mut across = vec![0u8; 20];
source.read_at(512 - 10, &mut across).unwrap();
let mut first = vec![0u8; 512];
let mut second = vec![0u8; 512];
source.read_at(0, &mut first).unwrap();
source.read_at(512, &mut second).unwrap();
assert_eq!(&across[..10], &first[502..512]);
assert_eq!(&across[10..], &second[..10]);
}
#[test]
fn the_database_is_as_large_as_the_last_commit_says() {
let main = std::fs::read(data_path("wal_mode.sqlite")).unwrap();
let log = std::fs::read(data_path("wal_mode.sqlite-wal")).unwrap();
assert_eq!(main.len(), 1024);
let source: WalSource<SliceSource<'_>, SliceSource<'_>> =
WalSource::new(SliceSource::new(&main), Some(SliceSource::new(&log))).unwrap();
use quantydb_sqlite::Source;
assert_eq!(source.len().unwrap(), 11 * 512);
assert!(source.has_log());
}