use crate::btree::encode_sqlite_varint;
use crate::error::GpkgError;
use super::{PAGE_SIZE, page_allocator::PageAllocator};
const PAGE_TYPE_LEAF: u8 = 0x0D;
const LEAF_HEADER_BYTES: usize = 8;
pub const MAX_PAYLOAD_INLINE: usize = PAGE_SIZE - 35;
pub struct LeafPageBuilder {
rows: Vec<(i64, Vec<u8>)>,
current_used: usize,
}
impl LeafPageBuilder {
pub fn new() -> Self {
Self {
rows: Vec::new(),
current_used: 0,
}
}
pub fn try_add(&mut self, rowid: i64, payload: Vec<u8>) -> bool {
let pl_varint = encode_sqlite_varint(payload.len() as u64);
let rid_varint = encode_sqlite_varint(rowid as u64);
let cell_bytes = pl_varint.len() + rid_varint.len() + payload.len();
let needed = 2 + cell_bytes;
let worst_header_offset = 100;
let used_so_far = worst_header_offset
+ LEAF_HEADER_BYTES
+ self.rows.len() * 2 + self.current_used;
if used_so_far + needed > PAGE_SIZE {
return false;
}
self.current_used += cell_bytes;
self.rows.push((rowid, payload));
true
}
pub fn emit(&self, header_offset: usize) -> Vec<u8> {
let mut page = vec![0u8; PAGE_SIZE];
let cell_count = self.rows.len();
let mut content_end = PAGE_SIZE;
let mut cell_offsets: Vec<usize> = Vec::with_capacity(cell_count);
for (rowid, payload) in &self.rows {
let pl_varint = encode_sqlite_varint(payload.len() as u64);
let rid_varint = encode_sqlite_varint(*rowid as u64);
let cell_size = pl_varint.len() + rid_varint.len() + payload.len();
content_end -= cell_size;
let start = content_end;
cell_offsets.push(start);
let mut pos = start;
page[pos..pos + pl_varint.len()].copy_from_slice(&pl_varint);
pos += pl_varint.len();
page[pos..pos + rid_varint.len()].copy_from_slice(&rid_varint);
pos += rid_varint.len();
page[pos..pos + payload.len()].copy_from_slice(payload);
}
let hdr = header_offset;
page[hdr] = PAGE_TYPE_LEAF;
page[hdr + 1] = 0; page[hdr + 2] = 0; let cc = cell_count as u16;
page[hdr + 3] = (cc >> 8) as u8;
page[hdr + 4] = (cc & 0xFF) as u8;
let content_start_val = content_end as u16;
page[hdr + 5] = (content_start_val >> 8) as u8;
page[hdr + 6] = (content_start_val & 0xFF) as u8;
page[hdr + 7] = 0;
let ptr_start = hdr + LEAF_HEADER_BYTES;
for (i, &offset) in cell_offsets.iter().enumerate() {
let o = offset as u16;
page[ptr_start + i * 2] = (o >> 8) as u8;
page[ptr_start + i * 2 + 1] = (o & 0xFF) as u8;
}
page
}
}
impl Default for LeafPageBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn write_table(
allocator: &mut PageAllocator,
rows: &[(i64, Vec<u8>)],
header_offset: usize,
) -> Result<u32, GpkgError> {
let page_num = allocator.alloc();
let mut builder = LeafPageBuilder::new();
for (rowid, payload) in rows {
if payload.len() > MAX_PAYLOAD_INLINE {
return Err(GpkgError::RowOverflowsPage {
size: payload.len(),
max: MAX_PAYLOAD_INLINE,
});
}
if !builder.try_add(*rowid, payload.clone()) {
return Err(GpkgError::RowOverflowsPage {
size: payload.len(),
max: MAX_PAYLOAD_INLINE,
});
}
}
let page_bytes = builder.emit(header_offset);
allocator.write(page_num, page_bytes);
Ok(page_num)
}