use std::io::{self, Write};
use super::codec::{compress_int, compress_long};
pub const PAGE_SIZE_INT: usize = 1_000_000;
pub const PAGE_SIZE_LONG: usize = 500_000;
#[allow(dead_code)]
pub struct IntIndexStreamer<W: Write> {
w: W,
page: Vec<i32>,
page_starts: Vec<i64>,
written: i64,
size: i64,
}
#[allow(dead_code)]
impl<W: Write> IntIndexStreamer<W> {
pub fn new(w: W) -> Self {
Self::with_position(w, 0)
}
pub fn with_position(w: W, start: i64) -> Self {
Self {
w,
page: Vec::with_capacity(PAGE_SIZE_INT),
page_starts: vec![start],
written: start,
size: 0,
}
}
pub fn push(&mut self, v: i32) -> io::Result<()> {
self.page.push(v);
self.size += 1;
if self.page.len() == PAGE_SIZE_INT {
self.flush_page()?;
}
Ok(())
}
fn flush_page(&mut self) -> io::Result<()> {
let bytes = compress_int(&self.page);
self.w.write_all(&bytes)?;
self.written += bytes.len() as i64;
self.page_starts.push(self.written);
self.page.clear();
Ok(())
}
pub fn finish(mut self) -> io::Result<W> {
if !self.page.is_empty() {
self.flush_page()?;
}
write_footer(
&mut self.w,
&self.page_starts,
PAGE_SIZE_INT as i32,
self.size,
true,
)?;
Ok(self.w)
}
}
#[allow(dead_code)]
pub struct LongIndexStreamer<W: Write> {
w: W,
page: Vec<i64>,
page_starts: Vec<i64>,
written: i64,
size: i64,
}
#[allow(dead_code)]
impl<W: Write> LongIndexStreamer<W> {
pub fn new(w: W) -> Self {
Self {
w,
page: Vec::with_capacity(PAGE_SIZE_LONG),
page_starts: vec![0],
written: 0,
size: 0,
}
}
pub fn push(&mut self, v: i64) -> io::Result<()> {
self.page.push(v);
self.size += 1;
if self.page.len() == PAGE_SIZE_LONG {
self.flush_page()?;
}
Ok(())
}
fn flush_page(&mut self) -> io::Result<()> {
let bytes = compress_long(&self.page);
self.w.write_all(&bytes)?;
self.written += bytes.len() as i64;
self.page_starts.push(self.written);
self.page.clear();
Ok(())
}
pub fn finish(mut self) -> io::Result<W> {
if !self.page.is_empty() {
self.flush_page()?;
}
write_footer(
&mut self.w,
&self.page_starts,
PAGE_SIZE_LONG as i32,
self.size,
false,
)?;
Ok(self.w)
}
}
fn write_footer<W: Write>(
w: &mut W,
page_starts: &[i64],
page_size: i32,
size: i64,
huge_form: bool,
) -> io::Result<()> {
for &ps in page_starts {
w.write_all(&ps.to_be_bytes())?;
}
w.write_all(&page_size.to_be_bytes())?;
let s: i32 = if huge_form && size > i32::MAX as i64 {
let ps = page_size as i64;
-(((size + ps - 1) % ps) + 1) as i32
} else {
size as i32
};
w.write_all(&s.to_be_bytes())?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mat::codec::decode_long;
fn parse_footer(buf: &[u8]) -> (usize, i32, i64, Vec<i64>) {
let n = buf.len();
let size = i32::from_be_bytes(buf[n - 4..n].try_into().unwrap());
let page_size = i32::from_be_bytes(buf[n - 8..n - 4].try_into().unwrap());
let value_count = size as i64;
let pages = (value_count as usize).div_ceil(page_size as usize);
let entries = pages + 1;
let footer_start = n - 8 - entries * 8;
let mut page_starts = Vec::with_capacity(entries);
for i in 0..entries {
let off = footer_start + i * 8;
page_starts.push(i64::from_be_bytes(buf[off..off + 8].try_into().unwrap()));
}
(pages, page_size, value_count, page_starts)
}
#[test]
fn int_three_values() {
let mut s = IntIndexStreamer::new(Vec::new());
for v in [10i32, 20, 30] {
s.push(v).unwrap();
}
let buf = s.finish().unwrap();
let (pages, page_size, size, page_starts) = parse_footer(&buf);
assert_eq!(size, 3);
assert_eq!(page_size, PAGE_SIZE_INT as i32);
assert_eq!(pages, 1);
assert_eq!(page_starts.len(), 2);
assert_eq!(page_starts[0], 0);
let body_len = (buf.len() - 8 - page_starts.len() * 8) as i64;
assert_eq!(*page_starts.last().unwrap(), body_len);
}
#[test]
fn long_three_values() {
let mut s = LongIndexStreamer::new(Vec::new());
for v in [0x1000i64, 0x2000, 0x1_0000_0000] {
s.push(v).unwrap();
}
let buf = s.finish().unwrap();
let (pages, page_size, size, page_starts) = parse_footer(&buf);
assert_eq!(size, 3);
assert_eq!(page_size, PAGE_SIZE_LONG as i32);
assert_eq!(pages, 1);
assert_eq!(page_starts.len(), 2);
assert_eq!(page_starts[0], 0);
let body_len = (buf.len() - 8 - page_starts.len() * 8) as i64;
assert_eq!(*page_starts.last().unwrap(), body_len);
}
#[test]
fn matches_real_idx() {
let path = "/tmp/matidx/dump_.idx.index";
let Ok(real) = std::fs::read(path) else {
eprintln!("skip matches_real_idx: fixture absent at {path}");
return;
};
let (pages, page_size, size, page_starts) = parse_footer(&real);
assert_eq!(page_size, PAGE_SIZE_LONG as i32, "fixture pageSize");
let mut values: Vec<i64> = Vec::with_capacity(size as usize);
for i in 0..pages {
let start = page_starts[i] as usize;
let end = page_starts[i + 1] as usize;
let n = std::cmp::min(PAGE_SIZE_LONG, size as usize - i * PAGE_SIZE_LONG);
let decoded = decode_long(&real[start..end], n);
values.extend_from_slice(&decoded);
}
assert_eq!(values.len() as i64, size, "decoded value count");
let mut s = LongIndexStreamer::new(Vec::new());
for &v in &values {
s.push(v).unwrap();
}
let ours = s.finish().unwrap();
if ours != real {
let (mut off, min) = (usize::MAX, std::cmp::min(ours.len(), real.len()));
for i in 0..min {
if ours[i] != real[i] {
off = i;
break;
}
}
let body_len = *page_starts.last().unwrap() as usize;
let region = if off == usize::MAX {
"length differs".to_string()
} else if off < body_len {
format!("body (page-relative area, body_len={body_len})")
} else {
"footer".to_string()
};
panic!(
"byte mismatch: ours.len={} real.len={} first diff at offset {} in {}",
ours.len(),
real.len(),
off,
region
);
}
assert_eq!(ours, real);
}
}