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 usable = wp.usable_size();
let page_size = usable + wp.header().reserved_space as usize;
let root = wp.allocate_page()?;
let buf = serialize_index_leaf(page_size, usable, 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)?;
match insert_rec(wp, root, record, rcell, colls, descs)? {
IdxUp::Fit => {}
IdxUp::Split(split) => grow_root(wp, root, split)?,
IdxUp::LeafOverfull(entries) => deepen_leaf_root(wp, root, entries)?,
}
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, usable, 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>);
enum IdxUp {
Fit,
LeafOverfull(Vec<LeafEntry>),
Split(IdxSplit),
}
fn insert_rec(
wp: &mut WritePager,
page_no: u32,
target: &[u8],
rcell: Vec<u8>,
colls: &[Collation],
descs: &[bool],
) -> Result<IdxUp> {
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(IdxUp::Fit), Ordering::Greater => {}
}
}
entries.insert(pos, (target.to_vec(), rcell));
if leaf_fits(&entries, body, usable) {
let prefix = page_one_prefix(page_no, &bt);
let buf = serialize_index_leaf(
page_size,
usable,
body,
&rcells(&entries),
prefix.as_deref(),
)?;
wp.write_page(page_no, buf)?;
Ok(IdxUp::Fit)
} else {
Ok(IdxUp::LeafOverfull(entries))
}
}
PageType::InteriorIndex => {
let (cells, right) = read_interior(wp, &bt, usable)?;
let mut children: Vec<u32> = Vec::with_capacity(cells.len() + 1);
let mut dividers: Vec<IdxKey> = Vec::with_capacity(cells.len());
for (c, full, rc) in cells {
children.push(c);
dividers.push((full, rc));
}
children.push(right);
let prefix = page_one_prefix(page_no, &bt);
drop(bt);
let mut p = children.len() - 1;
for (i, (full, _)) in dividers.iter().enumerate() {
match cmp_records(target, full, enc, colls, descs)? {
Ordering::Less => {
p = i;
break;
}
Ordering::Equal => return Ok(IdxUp::Fit),
Ordering::Greater => {}
}
}
match insert_rec(wp, children[p], target, rcell, colls, descs)? {
IdxUp::Fit => return Ok(IdxUp::Fit), IdxUp::LeafOverfull(child_entries) => {
balance_leaf_into(wp, &mut children, &mut dividers, p, child_entries)?;
}
IdxUp::Split(s) => adopt_split(&mut children, &mut dividers, p, s),
}
finish_interior(
wp, page_no, body, page_size, usable, &children, ÷rs, prefix,
)
}
_ => Err(Error::Corrupt("insert into a non-index b-tree".into())),
}
}
fn adopt_split(children: &mut Vec<u32>, dividers: &mut Vec<IdxKey>, p: usize, s: IdxSplit) {
let mut sibs = s.siblings;
if p < dividers.len() {
let old_div = dividers[p].clone();
let mut new_divs = Vec::with_capacity(sibs.len() + 1);
new_divs.push(s.first);
for (k, _) in &sibs[..sibs.len() - 1] {
new_divs.push(k.clone());
}
new_divs.push(old_div);
let new_children: Vec<u32> = sibs.iter().map(|(_, pg)| *pg).collect();
children.splice(p + 1..p + 1, new_children);
dividers.splice(p..p + 1, new_divs);
} else {
let last = sibs.pop().expect("split always has a sibling");
dividers.push(s.first);
for (k, _) in &sibs {
dividers.push(k.clone());
}
for (_, pg) in &sibs {
children.push(*pg);
}
children.push(last.1);
}
}
#[allow(clippy::too_many_arguments)]
fn finish_interior(
wp: &mut WritePager,
page_no: u32,
body: usize,
page_size: usize,
usable: usize,
children: &[u32],
dividers: &[IdxKey],
prefix: Option<Vec<u8>>,
) -> Result<IdxUp> {
let mut cells: Vec<InteriorEntry> = Vec::with_capacity(dividers.len());
for i in 0..dividers.len() {
cells.push((children[i], dividers[i].0.clone(), dividers[i].1.clone()));
}
let right = *children.last().expect("interior always has a right child");
if interior_fits(&cells, body, usable) {
let buf =
serialize_index_interior(page_size, usable, body, &cells, right, prefix.as_deref())?;
wp.write_page(page_no, buf)?;
return Ok(IdxUp::Fit);
}
let (parts, seps) = pack_index_interior(&cells, right, body, usable);
if parts.len() == 1 {
let (pc, pr) = &parts[0];
let buf = serialize_index_interior(page_size, usable, body, pc, *pr, prefix.as_deref())?;
wp.write_page(page_no, buf)?;
return Ok(IdxUp::Fit);
}
let first = seps[0].clone();
let (p0c, p0r) = &parts[0];
let lbuf = serialize_index_interior(page_size, usable, 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, usable, 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(IdxUp::Split(IdxSplit { first, siblings }))
}
fn balance_leaf_into(
wp: &mut WritePager,
children: &mut Vec<u32>,
dividers: &mut Vec<IdxKey>,
p: usize,
child_entries: Vec<LeafEntry>,
) -> Result<()> {
let (w0, n_old) = super::balance::sibling_window(p, children.len());
let usable = wp.usable_size();
let old_pages: Vec<u32> = children[w0..w0 + n_old].to_vec();
let mut pooled: Vec<LeafEntry> = Vec::new();
let mut cnt_old: Vec<usize> = Vec::with_capacity(n_old);
for (offset, &pg) in old_pages.iter().enumerate() {
if w0 + offset == p {
pooled.extend(child_entries.iter().cloned());
} else {
let bt = BtreePage::parse(wp.page(pg)?)?;
pooled.extend(read_leaf(wp, &bt, usable)?);
}
cnt_old.push(pooled.len());
if offset + 1 < n_old {
pooled.push(dividers[w0 + offset].clone());
}
}
let (pages, new_dividers) = balance_leaf_pooled(wp, &old_pages, &pooled, &cnt_old)?;
children.splice(w0..w0 + n_old, pages);
dividers.splice(w0..w0 + n_old - 1, new_dividers);
Ok(())
}
fn balance_leaf_pooled(
wp: &mut WritePager,
old_pages: &[u32],
pooled: &[LeafEntry],
cnt_old: &[usize],
) -> Result<(Vec<u32>, Vec<IdxKey>)> {
let usable = wp.usable_size();
let page_size = usable + wp.header().reserved_space as usize;
let sz: Vec<usize> = pooled.iter().map(|(_, c)| c.len()).collect();
let cnt_new = super::balance::distribute(&sz, cnt_old, true, false, usable);
let n_new = cnt_new.len();
let n_old = old_pages.len();
let mut kept: Vec<u32> = old_pages.iter().take(n_new).copied().collect();
for _ in n_old..n_new {
kept.push(wp.allocate_page()?);
}
for &pg in &old_pages[n_new.min(n_old)..] {
wp.free_page(pg)?;
}
kept.sort_unstable();
let mut new_dividers = Vec::with_capacity(n_new.saturating_sub(1));
let mut start = 0usize;
for (i, &end) in cnt_new.iter().enumerate() {
let slice = &pooled[start..end];
let buf = serialize_index_leaf(page_size, usable, 0, &rcells(slice), None)?;
wp.write_page(kept[i], buf)?;
if i < n_new - 1 {
new_dividers.push(pooled[end].clone());
}
start = end + 1; }
Ok((kept, new_dividers))
}
fn deepen_leaf_root(wp: &mut WritePager, root: u32, entries: Vec<LeafEntry>) -> Result<()> {
let usable = wp.usable_size();
let page_size = usable + wp.header().reserved_space as usize;
let child0 = wp.allocate_page()?;
let cnt_old = vec![entries.len()];
let (pages, dividers) = balance_leaf_pooled(wp, &[child0], &entries, &cnt_old)?;
let mut icells: Vec<InteriorEntry> = Vec::with_capacity(dividers.len());
for (i, (full, rc)) in dividers.into_iter().enumerate() {
icells.push((pages[i], full, rc));
}
let right = *pages.last().expect("deepen always yields >= 2 leaves");
let buf = serialize_index_interior(page_size, usable, 0, &icells, right, None)?;
wp.write_page(root, buf)?;
Ok(())
}
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, usable, 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, usable: usize) -> bool {
let used: usize = entries.iter().map(|(_, c)| c.len() + 2).sum();
used <= usable - body - 8
}
fn interior_fits(cells: &[InteriorEntry], body: usize, usable: usize) -> bool {
let used: usize = cells.iter().map(|(_, _, c)| 4 + c.len() + 2).sum();
used <= usable - body - 12
}
type IdxInteriorPart = (Vec<InteriorEntry>, u32);
fn pack_index_interior(
cells: &[InteriorEntry],
right: u32,
body0: usize,
usable: 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 = usable.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,
usable: 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 = usable;
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,
usable: 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 = usable;
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::*;
use crate::format::record::encode_record;
use crate::vfs::{OpenFlags, Vfs, memory::MemoryVfs};
#[test]
fn deep_index_random_order_is_compact_and_ordered() {
let vfs = MemoryVfs::new();
let f = vfs.open("db", OpenFlags::READ_WRITE_CREATE).unwrap();
let mut wp = WritePager::create(f, None, 512).unwrap();
let root = create_index_root(&mut wp).unwrap();
let n: i64 = 5001;
for i in 0..n {
let key = (i.wrapping_mul(104729)).rem_euclid(n);
let rec = encode_record(&[Value::Integer(key), Value::Integer(i)]);
insert_index(&mut wp, root, &rec, &[], &[]).unwrap();
}
let recs = index_range_records(&wp, root, None, None, &[], &[]).unwrap();
assert_eq!(recs.len(), n as usize);
let mut prev = -1i64;
for r in &recs {
let key = match r[0] {
Value::Integer(k) => k,
_ => panic!("bad key"),
};
assert!(key > prev, "index out of order: {key} after {prev}");
prev = key;
}
assert!(
wp.page_count() < 300,
"index fragmented: {} pages for {n} entries",
wp.page_count()
);
}
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_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, page, 0, pc, *pr, None).unwrap();
}
assert_eq!(parts.last().unwrap().1, right);
assert!(children.contains(&right));
}
}