use std::cmp::Ordering;
use std::io::Cursor;
use riegeli::{
CompressionType, Field, FieldProjection, ReaderOptions, RecordReader, RecordWriter,
WriterOptions,
};
fn write_records(records: &[Vec<u8>], opts: WriterOptions) -> Vec<u8> {
let mut buf = Cursor::new(Vec::<u8>::new());
{
let mut w = RecordWriter::new(&mut buf, opts).expect("writer new ok");
for rec in records {
w.write_record(rec).expect("write ok");
}
w.flush().expect("flush ok");
}
buf.into_inner()
}
fn encode_sorted_record(value: u64) -> Vec<u8> {
value.to_be_bytes().to_vec()
}
fn parse_sorted_record(record: &[u8]) -> Option<u64> {
if record.len() == 8 {
Some(u64::from_be_bytes(record.try_into().unwrap()))
} else {
None
}
}
fn compare_record(record: &[u8], target: u64) -> Ordering {
match parse_sorted_record(record) {
Some(v) => v.cmp(&target),
None => Ordering::Less,
}
}
#[test]
fn adv_search_single_record_found() {
let data = write_records(
&[encode_sorted_record(42)],
WriterOptions::new().compression(CompressionType::None),
);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader.search(|rec| compare_record(rec, 42)).expect("ok");
assert!(found, "single-record file: should find record 42");
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
assert_eq!(
parse_sorted_record(&next),
Some(42),
"next read after search should return 42"
);
}
#[test]
fn adv_search_single_record_not_found_less() {
let data = write_records(
&[encode_sorted_record(42)],
WriterOptions::new().compression(CompressionType::None),
);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader.search(|rec| compare_record(rec, 10)).expect("ok");
assert!(
!found,
"single-record file: should not find record 10 (only record is 42)"
);
let next = reader.read_record().expect("ok");
assert!(
next.is_none(),
"reader should be at EOF after failed search"
);
}
#[test]
fn adv_search_single_record_not_found_greater() {
let data = write_records(
&[encode_sorted_record(42)],
WriterOptions::new().compression(CompressionType::None),
);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader.search(|rec| compare_record(rec, 100)).expect("ok");
assert!(
!found,
"single-record file: should not find record 100 (only record is 42)"
);
let next = reader.read_record().expect("ok");
assert!(
next.is_none(),
"reader should be at EOF after failed search"
);
}
#[test]
fn adv_search_after_partial_read() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.chunk_size(256);
let records: Vec<Vec<u8>> = (0..100u64).map(encode_sorted_record).collect();
let data = write_records(&records, opts);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
for _ in 0..10 {
reader
.read_record()
.expect("ok")
.expect("should have record");
}
let found = reader.search(|rec| compare_record(rec, 5)).expect("ok");
assert!(
found,
"search should find record 5 even after reading past it"
);
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
assert_eq!(
parse_sorted_record(&next),
Some(5),
"next read after search(5) should return 5"
);
}
#[test]
fn adv_search_with_metadata_chunk() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.set_serialized_metadata(b"schema-v1".to_vec());
let records: Vec<Vec<u8>> = (0..50u64).map(encode_sorted_record).collect();
let data = write_records(&records, opts);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader.search(|rec| compare_record(rec, 25)).expect("ok");
assert!(found, "search should find record 25 despite metadata chunk");
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
assert_eq!(
parse_sorted_record(&next),
Some(25),
"next read after search should return 25"
);
}
#[test]
fn adv_search_two_chunk_target_in_second() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.chunk_size(32);
let records: Vec<Vec<u8>> = (0..8u64).map(encode_sorted_record).collect();
let data = write_records(&records, opts);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader.search(|rec| compare_record(rec, 7)).expect("ok");
assert!(found, "should find record 7");
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
assert_eq!(parse_sorted_record(&next), Some(7), "next read should be 7");
}
#[test]
fn adv_search_value_between_chunk_pivots() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.chunk_size(24); let records: Vec<Vec<u8>> = (0..10u64).map(encode_sorted_record).collect();
let data = write_records(&records, opts);
for target in 0u64..10u64 {
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader
.search(|rec| compare_record(rec, target))
.expect("ok");
assert!(found, "should find record {}", target);
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
assert_eq!(
parse_sorted_record(&next),
Some(target),
"next read should be {}",
target
);
}
}
#[test]
fn adv_set_field_projection_before_first_read() {
let encode_proto = |f1: u64, f2: u32| -> Vec<u8> {
let mut rec = Vec::new();
let tag1 = 1u32 << 3; rec.push(tag1 as u8);
rec.push(f1 as u8); let tag2 = 2u32 << 3; rec.push(tag2 as u8);
rec.push(f2 as u8);
rec
};
let opts = WriterOptions::new()
.compression(CompressionType::None)
.transpose(true);
let records: Vec<Vec<u8>> = (0u8..10u8)
.map(|i| encode_proto(i as u64, (i as u32) + 100))
.collect();
let data = write_records(&records, opts);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let proj = FieldProjection::new().add_field(Field::new(vec![1]));
reader.set_field_projection(proj);
let full_record_size = encode_proto(5, 105).len();
let mut all_records = Vec::new();
while let Some(rec) = reader.read_record().expect("ok") {
all_records.push(rec);
}
assert!(!all_records.is_empty(), "should have read some records");
let has_projected = all_records.iter().any(|r| r.len() < full_record_size);
assert!(
has_projected,
"some records should be projected (shorter than full {} bytes)",
full_record_size
);
}
#[test]
fn adv_search_duplicate_values() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.chunk_size(256);
let records: Vec<Vec<u8>> = vec![0u64, 0, 5, 5, 10, 10]
.into_iter()
.map(encode_sorted_record)
.collect();
let data = write_records(&records, opts);
for target in &[0u64, 5, 10] {
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found = reader
.search(|rec| compare_record(rec, *target))
.expect("ok");
assert!(found, "should find target {}", target);
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
let val = parse_sorted_record(&next).expect("parse ok");
assert_eq!(val, *target, "next read should return target {}", target);
}
}
#[test]
fn adv_search_twice_second_after_eof() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.chunk_size(256);
let records: Vec<Vec<u8>> = (0..20u64).map(encode_sorted_record).collect();
let data = write_records(&records, opts);
let mut reader = RecordReader::new(Cursor::new(&data), ReaderOptions::new()).expect("ok");
let found1 = reader.search(|rec| compare_record(rec, 99)).expect("ok");
assert!(!found1, "search(99) should fail");
let found2 = reader.search(|rec| compare_record(rec, 10)).expect("ok");
assert!(
found2,
"search(10) should succeed after a prior failed search"
);
let next = reader
.read_record()
.expect("ok")
.expect("should have record");
assert_eq!(
parse_sorted_record(&next),
Some(10),
"next read should be 10"
);
}