use quantydb_sqlite::{Reader, SliceSource, SqliteError, SqliteValue, TextEncoding};
fn fixture(name: &str) -> Vec<u8> {
let path = format!("{}/tests/data/{name}", env!("CARGO_MANIFEST_DIR"));
std::fs::read(&path).unwrap_or_else(|e| panic!("fixture {path}: {e}"))
}
fn expected() -> Vec<String> {
vec![
"ascii".to_string(),
"\u{fc}ber \u{e4}nderung".to_string(),
"\u{65e5}\u{672c}\u{8a9e}".to_string(),
"\u{1f600} emoji".to_string(),
"\u{1f3f4}\u{e0067}\u{e0062}\u{e0073}\u{e0063}\u{e0074}\u{e007f}".to_string(),
String::new(),
"a".repeat(2000),
"\u{1f600}".repeat(400),
]
}
fn read_values(bytes: &[u8]) -> Vec<String> {
let reader = Reader::open(SliceSource::new(bytes)).unwrap();
let root = reader
.schema()
.unwrap()
.object("t")
.unwrap()
.root_page
.unwrap();
reader
.rows(root)
.unwrap()
.map(|row| match &row.unwrap().values[1] {
SqliteValue::Text(t) => t.clone(),
other => panic!("expected text, got {other:?}"),
})
.collect()
}
#[test]
fn the_header_names_the_encoding() {
let bytes = fixture("utf16le.sqlite");
let le = Reader::open(SliceSource::new(&bytes)).unwrap();
assert_eq!(le.header().text_encoding, TextEncoding::Utf16Le);
let bytes = fixture("utf16be.sqlite");
let be = Reader::open(SliceSource::new(&bytes)).unwrap();
assert_eq!(be.header().text_encoding, TextEncoding::Utf16Be);
}
#[test]
fn both_byte_orders_produce_the_same_strings() {
let le = read_values(&fixture("utf16le.sqlite"));
let be = read_values(&fixture("utf16be.sqlite"));
assert_eq!(le, expected(), "little endian");
assert_eq!(be, expected(), "big endian");
assert_eq!(le, be);
}
#[test]
fn the_two_files_really_do_differ_on_disk() {
let le = fixture("utf16le.sqlite");
let be = fixture("utf16be.sqlite");
assert_eq!(le.len(), be.len());
assert_ne!(le, be, "the fixtures should differ byte for byte");
}
#[test]
fn characters_outside_the_basic_plane_survive() {
for name in ["utf16le.sqlite", "utf16be.sqlite"] {
let values = read_values(&fixture(name));
assert_eq!(values[3], "\u{1f600} emoji", "{name}");
assert_eq!(values[3].chars().count(), 7, "{name}: seven characters");
assert_eq!(values[4].chars().count(), 7, "{name}: a tag sequence flag");
}
}
#[test]
fn a_long_value_spills_and_comes_back_whole() {
for name in ["utf16le.sqlite", "utf16be.sqlite"] {
let values = read_values(&fixture(name));
assert_eq!(values[6].len(), 2000, "{name}");
assert!(values[6].chars().all(|c| c == 'a'), "{name}");
assert_eq!(values[7].chars().count(), 400, "{name}");
assert!(values[7].chars().all(|c| c == '\u{1f600}'), "{name}");
}
}
#[test]
fn an_unpaired_surrogate_is_refused_rather_than_replaced() {
let mut bytes = fixture("utf16le.sqlite");
let needle: Vec<u8> = "ascii"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let at = bytes
.windows(needle.len())
.position(|window| window == needle)
.expect("the stored string is in the file");
bytes[at] = 0x00;
bytes[at + 1] = 0xd8;
let reader = Reader::open(SliceSource::new(&bytes)).unwrap();
let root = reader
.schema()
.unwrap()
.object("t")
.unwrap()
.root_page
.unwrap();
let first = reader.rows(root).unwrap().next().unwrap();
match first {
Ok(row) => panic!("an unpaired surrogate was accepted as {:?}", row.values),
Err(e) => assert!(e.to_string().contains("surrogate"), "message was: {e}"),
}
}
#[test]
fn a_text_value_of_odd_length_is_refused() {
let payload = [2u8, 15, b'x'];
let err = quantydb_sqlite::decode_record(&payload, TextEncoding::Utf16Le)
.expect_err("an odd length utf-16 value was accepted");
assert!(matches!(err, SqliteError::Malformed { .. }));
assert!(err.to_string().contains("code unit"), "message was: {err}");
let value = quantydb_sqlite::decode_record(&payload, TextEncoding::Utf8).unwrap();
assert_eq!(value, vec![SqliteValue::Text("x".to_string())]);
}
#[test]
fn the_reader_supplies_the_encoding_itself() {
let bytes = fixture("utf16be.sqlite");
let reader = Reader::open(SliceSource::new(&bytes)).unwrap();
let root = reader
.schema()
.unwrap()
.object("t")
.unwrap()
.root_page
.unwrap();
let mut page = reader.btree_page(root).unwrap();
while page.kind.is_interior() {
page = match reader.cell(&page, 0).unwrap() {
quantydb_sqlite::Cell::TableInterior { child, .. } => reader.btree_page(child).unwrap(),
other => panic!("unexpected cell {other:?}"),
};
}
let payload = match reader.cell(&page, 0).unwrap() {
quantydb_sqlite::Cell::TableLeaf { payload, .. } => payload,
other => panic!("unexpected cell {other:?}"),
};
assert_eq!(
reader.decode_record(&payload).unwrap(),
quantydb_sqlite::decode_record(&payload, TextEncoding::Utf16Be).unwrap()
);
let wrong = quantydb_sqlite::decode_record(&payload, TextEncoding::Utf16Le);
if let Ok(values) = wrong {
assert_ne!(values, reader.decode_record(&payload).unwrap());
}
}