use super::page::{BtreePage, PageType, payload_split};
use super::writer::{page_one_prefix, write_overflow_chain};
use crate::btree::cursor::read_payload;
use crate::error::{Error, Result};
use crate::format::TextEncoding;
use crate::format::record::decode_record;
use crate::pager::{PageSource, WritePager};
use crate::util::varint;
use crate::value::{Collation, Value, cmp_values_coll};
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering;
type IdxKey = (Vec<u8>, Vec<u8>);
struct IdxSplit {
first: IdxKey,
siblings: Vec<(IdxKey, u32)>,
}
pub fn index_seek_rowids(
src: &dyn PageSource,
root: u32,
key: &[Value],
colls: &[Collation],
descs: &[bool],
) -> Result<Vec<i64>> {
let enc = src.header().text_encoding;
let usable = src.usable_size();
let mut out = Vec::new();
seek_prefix(src, root, key, enc, usable, colls, descs, &mut out)?;
Ok(out)
}
pub fn index_seek_records(
src: &dyn PageSource,
root: u32,
key: &[Value],
colls: &[Collation],
descs: &[bool],
) -> Result<Vec<Vec<Value>>> {
let enc = src.header().text_encoding;
let usable = src.usable_size();
let mut out = Vec::new();
seek_prefix_records(src, root, key, enc, usable, colls, descs, &mut out)?;
Ok(out)
}
pub fn index_range_rowids(
src: &dyn PageSource,
root: u32,
lower: Option<(&[Value], bool)>,
upper: Option<(&[Value], bool)>,
colls: &[Collation],
descs: &[bool],
) -> Result<Vec<i64>> {
let enc = src.header().text_encoding;
let usable = src.usable_size();
let mut out = Vec::new();
range_scan(
src,
root,
lower,
upper,
enc,
usable,
colls,
descs,
&mut |rec| {
out.push(rowid_of(&rec));
},
)?;
Ok(out)
}
pub fn index_range_records(
src: &dyn PageSource,
root: u32,
lower: Option<(&[Value], bool)>,
upper: Option<(&[Value], bool)>,
colls: &[Collation],
descs: &[bool],
) -> Result<Vec<Vec<Value>>> {
let enc = src.header().text_encoding;
let usable = src.usable_size();
let mut out = Vec::new();
range_scan(
src,
root,
lower,
upper,
enc,
usable,
colls,
descs,
&mut |rec| {
out.push(rec);
},
)?;
Ok(out)
}
fn passes_lower(
lower: Option<(&[Value], bool)>,
rec: &[Value],
colls: &[Collation],
descs: &[bool],
) -> bool {
match lower {
None => true,
Some((lk, inc)) => match prefix_cmp(lk, rec, colls, descs) {
Ordering::Greater => false, Ordering::Equal => inc, Ordering::Less => true, },
}
}
fn beyond_upper(
upper: Option<(&[Value], bool)>,
rec: &[Value],
colls: &[Collation],
descs: &[bool],
) -> bool {
match upper {
None => false,
Some((uk, inc)) => match prefix_cmp(uk, rec, colls, descs) {
Ordering::Less => true, Ordering::Equal => !inc, Ordering::Greater => false, },
}
}
#[allow(clippy::too_many_arguments)]
fn range_scan(
src: &dyn PageSource,
page_no: u32,
lower: Option<(&[Value], bool)>,
upper: Option<(&[Value], bool)>,
enc: TextEncoding,
usable: usize,
colls: &[Collation],
descs: &[bool],
collect: &mut dyn FnMut(Vec<Value>),
) -> Result<bool> {
let page = src.page(page_no)?;
let bt = BtreePage::parse(page)?;
let record = |i: usize| -> Result<Vec<Value>> {
let cell = bt.index_cell(i, usable)?;
let full = read_payload(src, bt.data(), &cell.payload)?;
decode_record(&full, enc)
};
match bt.page_type() {
PageType::LeafIndex => {
for i in 0..bt.num_cells() {
let rec = record(i)?;
if beyond_upper(upper, &rec, colls, descs) {
return Ok(false);
}
if passes_lower(lower, &rec, colls, descs) {
collect(rec);
}
}
Ok(true)
}
PageType::InteriorIndex => {
let n = bt.num_cells();
for k in 0..n {
if !range_scan(
src,
bt.child_pointer(k)?,
lower,
upper,
enc,
usable,
colls,
descs,
collect,
)? {
return Ok(false);
}
let rec = record(k)?;
if beyond_upper(upper, &rec, colls, descs) {
return Ok(false);
}
if passes_lower(lower, &rec, colls, descs) {
collect(rec);
}
}
range_scan(
src,
bt.child_pointer(n)?,
lower,
upper,
enc,
usable,
colls,
descs,
collect,
)
}
_ => Err(Error::Corrupt(
"index range scan on a non-index b-tree".into(),
)),
}
}
#[allow(clippy::too_many_arguments)]
fn seek_prefix(
src: &dyn PageSource,
page_no: u32,
key: &[Value],
enc: TextEncoding,
usable: usize,
colls: &[Collation],
descs: &[bool],
out: &mut Vec<i64>,
) -> Result<()> {
let page = src.page(page_no)?;
let bt = BtreePage::parse(page)?;
let record = |i: usize| -> Result<Vec<Value>> {
let cell = bt.index_cell(i, usable)?;
let full = read_payload(src, bt.data(), &cell.payload)?;
decode_record(&full, enc)
};
match bt.page_type() {
PageType::LeafIndex => {
for i in 0..bt.num_cells() {
let rec = record(i)?;
match prefix_cmp(key, &rec, colls, descs) {
Ordering::Greater => continue,
Ordering::Equal => out.push(rowid_of(&rec)),
Ordering::Less => break, }
}
Ok(())
}
PageType::InteriorIndex => {
let n = bt.num_cells();
let mut i = 0;
while i < n && prefix_cmp(key, &record(i)?, colls, descs) == Ordering::Greater {
i += 1;
}
seek_prefix(
src,
bt.child_pointer(i)?,
key,
enc,
usable,
colls,
descs,
out,
)?;
while i < n && prefix_cmp(key, &record(i)?, colls, descs) == Ordering::Equal {
out.push(rowid_of(&record(i)?));
seek_prefix(
src,
bt.child_pointer(i + 1)?,
key,
enc,
usable,
colls,
descs,
out,
)?;
i += 1;
}
Ok(())
}
_ => Err(Error::Corrupt("index seek on a non-index b-tree".into())),
}
}
#[allow(clippy::too_many_arguments)]
fn seek_prefix_records(
src: &dyn PageSource,
page_no: u32,
key: &[Value],
enc: TextEncoding,
usable: usize,
colls: &[Collation],
descs: &[bool],
out: &mut Vec<Vec<Value>>,
) -> Result<()> {
let page = src.page(page_no)?;
let bt = BtreePage::parse(page)?;
let record = |i: usize| -> Result<Vec<Value>> {
let cell = bt.index_cell(i, usable)?;
let full = read_payload(src, bt.data(), &cell.payload)?;
decode_record(&full, enc)
};
match bt.page_type() {
PageType::LeafIndex => {
for i in 0..bt.num_cells() {
let rec = record(i)?;
match prefix_cmp(key, &rec, colls, descs) {
Ordering::Greater => continue,
Ordering::Equal => out.push(rec),
Ordering::Less => break,
}
}
Ok(())
}
PageType::InteriorIndex => {
let n = bt.num_cells();
let mut i = 0;
while i < n && prefix_cmp(key, &record(i)?, colls, descs) == Ordering::Greater {
i += 1;
}
seek_prefix_records(
src,
bt.child_pointer(i)?,
key,
enc,
usable,
colls,
descs,
out,
)?;
while i < n && prefix_cmp(key, &record(i)?, colls, descs) == Ordering::Equal {
out.push(record(i)?);
seek_prefix_records(
src,
bt.child_pointer(i + 1)?,
key,
enc,
usable,
colls,
descs,
out,
)?;
i += 1;
}
Ok(())
}
_ => Err(Error::Corrupt("index seek on a non-index b-tree".into())),
}
}
fn prefix_cmp(key: &[Value], rec: &[Value], colls: &[Collation], descs: &[bool]) -> Ordering {
for (i, (k, r)) in key.iter().zip(rec.iter()).enumerate() {
let c = colls.get(i).copied().unwrap_or_default();
let o = cmp_values_coll(k, r, c);
let o = if descs.get(i).copied().unwrap_or(false) {
o.reverse()
} else {
o
};
if o != Ordering::Equal {
return o;
}
}
Ordering::Equal
}
fn rowid_of(rec: &[Value]) -> i64 {
match rec.last() {
Some(Value::Integer(i)) => *i,
_ => 0,
}
}
pub fn create_index_root(wp: &mut WritePager) -> Result<u32> {
let page_size = wp.usable_size() + wp.header().reserved_space as usize;
let root = wp.allocate_page()?;
let buf = serialize_index_leaf(page_size, 0, &[], None)?;
wp.write_page(root, buf)?;
Ok(root)
}
pub fn insert_index(
wp: &mut WritePager,
root: u32,
record: &[u8],
colls: &[Collation],
descs: &[bool],
) -> Result<()> {
let rcell = build_index_rcell(wp, record)?;
if let Some(split) = insert_rec(wp, root, record, rcell, colls, descs)? {
grow_root(wp, root, split)?;
}
Ok(())
}
pub fn free_tree(wp: &mut WritePager, root: u32) -> Result<()> {
let usable = wp.usable_size();
let page = wp.page(root)?;
let bt = BtreePage::parse(page)?;
match bt.page_type() {
PageType::LeafTable => {
for i in 0..bt.num_cells() {
let ov = bt.table_leaf_cell(i, usable)?.payload.overflow;
free_chain(wp, ov)?;
}
}
PageType::LeafIndex => {
for i in 0..bt.num_cells() {
let ov = bt.index_cell(i, usable)?.payload.overflow;
free_chain(wp, ov)?;
}
}
PageType::InteriorTable | PageType::InteriorIndex => {
let n = bt.num_cells();
for i in 0..n {
if bt.page_type() == PageType::InteriorIndex {
let ov = bt.index_cell(i, usable)?.payload.overflow;
free_chain(wp, ov)?;
}
free_tree(wp, bt.child_pointer(i)?)?;
}
free_tree(wp, bt.right_pointer())?;
}
}
wp.free_page(root)
}
pub fn clear_index(wp: &mut WritePager, root: u32) -> Result<()> {
let usable = wp.usable_size();
let page_size = usable + wp.header().reserved_space as usize;
let bt = BtreePage::parse(wp.page(root)?)?;
match bt.page_type() {
PageType::LeafIndex => {
for i in 0..bt.num_cells() {
free_chain(wp, bt.index_cell(i, usable)?.payload.overflow)?;
}
}
PageType::InteriorIndex => {
let n = bt.num_cells();
for i in 0..n {
free_chain(wp, bt.index_cell(i, usable)?.payload.overflow)?;
free_tree(wp, bt.child_pointer(i)?)?;
}
free_tree(wp, bt.right_pointer())?;
}
_ => return Err(Error::Corrupt("clear of a non-index b-tree".into())),
}
let empty = serialize_index_leaf(page_size, 0, &[], None)?;
wp.write_page(root, empty)?;
Ok(())
}
fn free_chain(wp: &mut WritePager, mut first: u32) -> Result<()> {
while first != 0 {
let page = wp.read_page(first)?;
let next = u32::from_be_bytes([page[0], page[1], page[2], page[3]]);
wp.free_page(first)?;
first = next;
}
Ok(())
}
fn build_index_rcell(wp: &mut WritePager, record: &[u8]) -> Result<Vec<u8>> {
let usable = wp.usable_size();
let (local, has_overflow) = payload_split(PageType::LeafIndex, usable, record.len());
let mut cell = Vec::new();
let mut vbuf = [0u8; varint::MAX_LEN];
let n = varint::encode(record.len() as u64, &mut vbuf);
cell.extend_from_slice(&vbuf[..n]);
cell.extend_from_slice(&record[..local]);
if has_overflow {
let first = write_overflow_chain(wp, &record[local..])?;
cell.extend_from_slice(&first.to_be_bytes());
}
Ok(cell)
}
type LeafEntry = (Vec<u8>, Vec<u8>); type InteriorEntry = (u32, Vec<u8>, Vec<u8>);
fn insert_rec(
wp: &mut WritePager,
page_no: u32,
target: &[u8],
rcell: Vec<u8>,
colls: &[Collation],
descs: &[bool],
) -> Result<Option<IdxSplit>> {
let enc = wp.header().text_encoding;
let page = wp.page(page_no)?;
let body = page.body_offset();
let bt = BtreePage::parse(page)?;
let usable = wp.usable_size();
let page_size = usable + wp.header().reserved_space as usize;
match bt.page_type() {
PageType::LeafIndex => {
let mut entries = read_leaf(wp, &bt, usable)?;
let mut pos = entries.len();
for (i, (full, _)) in entries.iter().enumerate() {
match cmp_records(target, full, enc, colls, descs)? {
Ordering::Less => {
pos = i;
break;
}
Ordering::Equal => return Ok(None), Ordering::Greater => {}
}
}
entries.insert(pos, (target.to_vec(), rcell));
let prefix = page_one_prefix(page_no, &bt);
if leaf_fits(&entries, body, page_size) {
let buf =
serialize_index_leaf(page_size, body, &rcells(&entries), prefix.as_deref())?;
wp.write_page(page_no, buf)?;
Ok(None)
} else {
let (parts, seps) = pack_index_leaf(&entries, body, page_size);
if parts.len() == 1 {
let buf = serialize_index_leaf(
page_size,
body,
&rcells(&entries[parts[0].clone()]),
prefix.as_deref(),
)?;
wp.write_page(page_no, buf)?;
return Ok(None);
}
let first = (entries[seps[0]].0.clone(), entries[seps[0]].1.clone());
let lbuf = serialize_index_leaf(
page_size,
body,
&rcells(&entries[parts[0].clone()]),
prefix.as_deref(),
)?;
wp.write_page(page_no, lbuf)?;
let mut siblings = Vec::with_capacity(parts.len() - 1);
for k in 1..parts.len() {
let pg = wp.allocate_page()?;
let buf = serialize_index_leaf(
page_size,
0,
&rcells(&entries[parts[k].clone()]),
None,
)?;
wp.write_page(pg, buf)?;
let key = if k < seps.len() {
(entries[seps[k]].0.clone(), entries[seps[k]].1.clone())
} else {
(Vec::new(), Vec::new())
};
siblings.push((key, pg));
}
Ok(Some(IdxSplit { first, siblings }))
}
}
PageType::InteriorIndex => {
let (mut cells, mut right) = read_interior(wp, &bt, usable)?;
let mut p = cells.len();
let mut child = right;
for (i, (c, full, _)) in cells.iter().enumerate() {
match cmp_records(target, full, enc, colls, descs)? {
Ordering::Less => {
p = i;
child = *c;
break;
}
Ordering::Equal => return Ok(None),
Ordering::Greater => {}
}
}
if let Some(s) = insert_rec(wp, child, target, rcell, colls, descs)? {
let mut sibs = s.siblings;
if p < cells.len() {
let old = cells[p].clone(); cells[p] = (old.0, s.first.0, s.first.1);
if let Some(last) = sibs.last_mut() {
last.0 = (old.1, old.2);
}
for (off, ((full, rc), pg)) in sibs.into_iter().enumerate() {
cells.insert(p + 1 + off, (pg, full, rc));
}
} else {
cells.push((child, s.first.0, s.first.1));
let last = sibs.pop().expect("split always has a sibling");
for ((full, rc), pg) in sibs {
cells.push((pg, full, rc));
}
right = last.1;
}
}
let prefix = page_one_prefix(page_no, &bt);
if interior_fits(&cells, body, page_size) {
let buf =
serialize_index_interior(page_size, body, &cells, right, prefix.as_deref())?;
wp.write_page(page_no, buf)?;
Ok(None)
} else {
let (parts, seps) = pack_index_interior(&cells, right, body, page_size);
if parts.len() == 1 {
let (pc, pr) = &parts[0];
let buf =
serialize_index_interior(page_size, body, pc, *pr, prefix.as_deref())?;
wp.write_page(page_no, buf)?;
return Ok(None);
}
let first = seps[0].clone();
let (p0c, p0r) = &parts[0];
let lbuf = serialize_index_interior(page_size, body, p0c, *p0r, prefix.as_deref())?;
wp.write_page(page_no, lbuf)?;
let mut siblings = Vec::with_capacity(parts.len() - 1);
for k in 1..parts.len() {
let (pc, pr) = &parts[k];
let pg = wp.allocate_page()?;
let buf = serialize_index_interior(page_size, 0, pc, *pr, None)?;
wp.write_page(pg, buf)?;
let key = if k < seps.len() {
seps[k].clone()
} else {
(Vec::new(), Vec::new())
};
siblings.push((key, pg));
}
Ok(Some(IdxSplit { first, siblings }))
}
}
_ => Err(Error::Corrupt("insert into a non-index b-tree".into())),
}
}
fn grow_root(wp: &mut WritePager, root: u32, split: IdxSplit) -> Result<()> {
let usable = wp.usable_size();
let page_size = usable + wp.header().reserved_space as usize;
let left_bytes = wp.read_page(root)?;
let new_left = wp.allocate_page()?;
wp.write_page(new_left, left_bytes)?;
let mut cells: Vec<InteriorEntry> = Vec::with_capacity(split.siblings.len());
cells.push((new_left, split.first.0, split.first.1));
let mut sibs = split.siblings;
let last = sibs.pop().expect("split always has a sibling");
for ((full, rc), pg) in sibs {
cells.push((pg, full, rc));
}
let buf = serialize_index_interior(page_size, 0, &cells, last.1, None)?;
wp.write_page(root, buf)?;
Ok(())
}
fn read_leaf(wp: &WritePager, bt: &BtreePage, usable: usize) -> Result<Vec<LeafEntry>> {
let mut out = Vec::with_capacity(bt.num_cells());
for i in 0..bt.num_cells() {
let cell = bt.index_cell(i, usable)?;
let full = read_payload(wp, bt.data(), &cell.payload)?;
let rcell = bt.raw_index_record_cell(i, usable)?.to_vec();
out.push((full, rcell));
}
Ok(out)
}
fn read_interior(
wp: &WritePager,
bt: &BtreePage,
usable: usize,
) -> Result<(Vec<InteriorEntry>, u32)> {
let mut out = Vec::with_capacity(bt.num_cells());
for i in 0..bt.num_cells() {
let cell = bt.index_cell(i, usable)?;
let full = read_payload(wp, bt.data(), &cell.payload)?;
let rcell = bt.raw_index_record_cell(i, usable)?.to_vec();
out.push((cell.left_child, full, rcell));
}
Ok((out, bt.right_pointer()))
}
fn rcells(entries: &[LeafEntry]) -> Vec<Vec<u8>> {
entries.iter().map(|(_, c)| c.clone()).collect()
}
fn cmp_records(
a: &[u8],
b: &[u8],
enc: TextEncoding,
colls: &[Collation],
descs: &[bool],
) -> Result<Ordering> {
let va = decode_record(a, enc)?;
let vb = decode_record(b, enc)?;
for (i, (x, y)) in va.iter().zip(vb.iter()).enumerate() {
let c = colls.get(i).copied().unwrap_or_default();
let o = cmp_values_coll(x, y, c);
let o = if descs.get(i).copied().unwrap_or(false) {
o.reverse()
} else {
o
};
if o != Ordering::Equal {
return Ok(o);
}
}
Ok(va.len().cmp(&vb.len()))
}
fn leaf_fits(entries: &[LeafEntry], body: usize, page_size: usize) -> bool {
let used: usize = entries.iter().map(|(_, c)| c.len() + 2).sum();
used <= page_size - body - 8
}
fn interior_fits(cells: &[InteriorEntry], body: usize, page_size: usize) -> bool {
let used: usize = cells.iter().map(|(_, _, c)| 4 + c.len() + 2).sum();
used <= page_size - body - 12
}
fn pack_index_leaf(
entries: &[LeafEntry],
body0: usize,
page_size: usize,
) -> (Vec<core::ops::Range<usize>>, Vec<usize>) {
let n = entries.len();
let mut parts: Vec<core::ops::Range<usize>> = Vec::new();
let mut seps: Vec<usize> = Vec::new();
let mut i = 0;
while i < n {
let body = if parts.is_empty() { body0 } else { 0 };
let cap = page_size.saturating_sub(body + 8);
let mut used = 0usize;
let mut j = i;
while j < n {
let need = entries[j].1.len() + 2;
if j == i || used + need <= cap {
used += need;
j += 1;
} else {
break;
}
}
if j >= n {
parts.push(i..j);
break;
}
if j + 1 >= n {
if j - 1 > i {
parts.push(i..j - 1);
seps.push(j - 1);
i = j; continue;
}
parts.push(i..n);
break;
}
parts.push(i..j);
seps.push(j);
i = j + 1;
}
(parts, seps)
}
type IdxInteriorPart = (Vec<InteriorEntry>, u32);
fn pack_index_interior(
cells: &[InteriorEntry],
right: u32,
body0: usize,
page_size: usize,
) -> (Vec<IdxInteriorPart>, Vec<IdxKey>) {
let n = cells.len();
let child = |j: usize| -> u32 { if j < n { cells[j].0 } else { right } };
let mut parts: Vec<IdxInteriorPart> = Vec::new();
let mut seps: Vec<IdxKey> = Vec::new();
let mut i = 0usize;
loop {
let body = if parts.is_empty() { body0 } else { 0 };
let cap = page_size.saturating_sub(body + 12);
let mut used = 0usize;
let mut b = i;
while b < n {
let need = 4 + cells[b].2.len() + 2;
if b == i || used + need <= cap {
used += need;
b += 1;
} else {
break;
}
}
if b >= n {
parts.push((cells[i..n].to_vec(), right));
break;
}
let mut bb = b;
if bb == n - 1 && bb > i + 1 {
bb -= 1;
}
parts.push((cells[i..bb].to_vec(), child(bb)));
seps.push((cells[bb].1.clone(), cells[bb].2.clone()));
i = bb + 1;
}
(parts, seps)
}
fn serialize_index_leaf(
page_size: usize,
body: usize,
rcells: &[Vec<u8>],
header_prefix: Option<&[u8]>,
) -> Result<Vec<u8>> {
let mut buf = vec![0u8; page_size];
if let Some(h) = header_prefix {
buf[..h.len()].copy_from_slice(h);
}
let ptr_base = body + 8;
let ptr_end = ptr_base + 2 * rcells.len();
let mut content = page_size;
for (i, cell) in rcells.iter().enumerate() {
if content < cell.len() || content - cell.len() < ptr_end {
return Err(Error::Corrupt(
"index leaf page overflow while serializing".into(),
));
}
content -= cell.len();
buf[content..content + cell.len()].copy_from_slice(cell);
let p = ptr_base + 2 * i;
buf[p] = (content >> 8) as u8;
buf[p + 1] = content as u8;
}
buf[body] = 0x0a; put16(&mut buf, body + 3, rcells.len() as u16);
put_ccs(&mut buf, body + 5, content);
Ok(buf)
}
fn serialize_index_interior(
page_size: usize,
body: usize,
cells: &[InteriorEntry],
right: u32,
header_prefix: Option<&[u8]>,
) -> Result<Vec<u8>> {
let mut buf = vec![0u8; page_size];
if let Some(h) = header_prefix {
buf[..h.len()].copy_from_slice(h);
}
let ptr_base = body + 12;
let ptr_end = ptr_base + 2 * cells.len();
let mut content = page_size;
for (i, (child, _, rcell)) in cells.iter().enumerate() {
let mut cell = Vec::with_capacity(4 + rcell.len());
cell.extend_from_slice(&child.to_be_bytes());
cell.extend_from_slice(rcell);
if content < cell.len() || content - cell.len() < ptr_end {
return Err(Error::Corrupt(
"index interior page overflow while serializing".into(),
));
}
content -= cell.len();
buf[content..content + cell.len()].copy_from_slice(&cell);
let p = ptr_base + 2 * i;
buf[p] = (content >> 8) as u8;
buf[p + 1] = content as u8;
}
buf[body] = 0x02; put16(&mut buf, body + 3, cells.len() as u16);
put_ccs(&mut buf, body + 5, content);
buf[body + 8..body + 12].copy_from_slice(&right.to_be_bytes());
Ok(buf)
}
fn put16(buf: &mut [u8], at: usize, v: u16) {
buf[at] = (v >> 8) as u8;
buf[at + 1] = v as u8;
}
fn put_ccs(buf: &mut [u8], at: usize, content: usize) {
let v = if content >= 65536 { 0 } else { content as u16 };
put16(buf, at, v);
}
#[cfg(test)]
mod tests {
use super::*;
fn leaf_entries(lens: &[usize]) -> Vec<LeafEntry> {
lens.iter()
.enumerate()
.map(|(i, &l)| (alloc::vec![i as u8], alloc::vec![0u8; l]))
.collect()
}
fn interior_cells(lens: &[usize]) -> Vec<InteriorEntry> {
lens.iter()
.enumerate()
.map(|(i, &l)| (i as u32 + 100, alloc::vec![i as u8], alloc::vec![0u8; l]))
.collect()
}
#[test]
fn pack_leaf_multiway_partitions_exactly() {
let page = 4096usize;
let entries = leaf_entries(&[1000; 12]);
let (parts, seps) = pack_index_leaf(&entries, 0, page);
assert!(parts.len() > 2, "expected a multi-way split, got {parts:?}");
assert_eq!(seps.len(), parts.len() - 1);
let mut seq = Vec::new();
for (k, r) in parts.iter().enumerate() {
assert!(!r.is_empty(), "empty part {k}");
seq.extend(r.clone());
if k < seps.len() {
seq.push(seps[k]);
}
}
assert_eq!(seq, (0..entries.len()).collect::<Vec<_>>());
for r in &parts {
let rc: Vec<Vec<u8>> = entries[r.clone()].iter().map(|(_, c)| c.clone()).collect();
assert!(leaf_fits(&entries[r.clone()], 0, page));
serialize_index_leaf(page, 0, &rc, None).unwrap();
}
}
#[test]
fn pack_leaf_front_heavy_fits() {
let page = 4096usize;
let mut lens = alloc::vec![1000usize; 4];
lens.extend([20usize; 8]);
let entries = leaf_entries(&lens);
let (parts, _seps) = pack_index_leaf(&entries, 0, page);
for r in &parts {
assert!(
leaf_fits(&entries[r.clone()], 0, page),
"part {r:?} overflows"
);
}
}
#[test]
fn pack_interior_multiway_partitions_exactly() {
let page = 4096usize;
let cells = interior_cells(&[1000; 12]);
let right = 9999u32;
let (parts, seps) = pack_index_interior(&cells, right, 0, page);
assert!(parts.len() > 2, "expected a multi-way interior split");
assert_eq!(seps.len(), parts.len() - 1);
let mut children = Vec::new();
for (k, (pc, pr)) in parts.iter().enumerate() {
for (c, _, _) in pc {
children.push(*c);
}
children.push(*pr); let _ = k;
serialize_index_interior(page, 0, pc, *pr, None).unwrap();
}
assert_eq!(parts.last().unwrap().1, right);
assert!(children.contains(&right));
}
#[test]
fn pack_leaf_small_page() {
let page = 512usize;
let entries = leaf_entries(&[100; 8]);
let (parts, seps) = pack_index_leaf(&entries, 0, page);
assert_eq!(seps.len(), parts.len() - 1);
for r in &parts {
assert!(!r.is_empty());
assert!(leaf_fits(&entries[r.clone()], 0, page));
}
}
}