use std::fs::File;
use std::io::Write;
use std::path::Path;
use super::ef_builder::EfBuilder;
use crate::error::{Error, Result};
use crate::seg::Seg;
pub const DEFAULT_BTREE_M: u64 = 256;
const BT_EF_ALIGN: usize = 4096;
const BT_FOOTER_ALIGN: usize = 8;
const BT_VERSION: u16 = 1;
const BT_METADATA_LEN: u32 = 24;
const FOOTER_MAGIC: [u8; 8] = *b"erigon\x00\x00";
const FIRST_BYTE_FOOTER: u8 = 0x01;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BtLayout {
Legacy,
Footer,
}
#[derive(Debug, Clone, Copy)]
pub struct BtOptions {
pub layout: BtLayout,
pub m: u64,
}
impl Default for BtOptions {
fn default() -> BtOptions {
BtOptions {
layout: BtLayout::Footer,
m: DEFAULT_BTREE_M,
}
}
}
pub fn build_bt(
kv_path: impl AsRef<Path>,
bt_path: impl AsRef<Path>,
opts: BtOptions,
) -> Result<()> {
let seg = Seg::open(kv_path)?;
build_bt_from_seg(&seg, bt_path, opts)
}
pub fn build_bt_from_seg(seg: &Seg, bt_path: impl AsRef<Path>, opts: BtOptions) -> Result<()> {
let bt_path = bt_path.as_ref();
let key_count = seg.words_count() / 2;
if key_count == 0 {
File::create(bt_path).map_err(|e| Error::io(bt_path, e))?;
return Ok(());
}
let max_offset = seg.len() as u64;
let m = opts.m.max(1);
let mut ef = EfBuilder::new(key_count, max_offset);
let mut nodes: Vec<u8> = Vec::new();
let footer = opts.layout == BtLayout::Footer;
if footer {
nodes.push(FIRST_BYTE_FOOTER);
}
let mut g = seg.getter();
for di in 0..key_count {
let off = g.offset();
if footer && di % m == 0 {
let key = g.next(); let klen = u16::try_from(key.len()).map_err(|_| {
Error::format("key longer than 65535 bytes (unsupported in .bt node)")
})?;
nodes.extend_from_slice(&klen.to_be_bytes());
nodes.extend_from_slice(&key);
} else {
g.skip(); }
g.skip(); ef.add_offset(off);
}
ef.build();
match opts.layout {
BtLayout::Legacy => {
let mut out = Vec::with_capacity(ef.serialized_len());
ef.write_to(&mut out);
write_all(bt_path, &out)
}
BtLayout::Footer => {
let mut out = nodes;
pad_to(&mut out, BT_EF_ALIGN);
let ef_offset = out.len() as u64;
ef.write_to(&mut out);
pad_to(&mut out, BT_FOOTER_ALIGN);
out.extend_from_slice(&key_count.to_be_bytes());
out.extend_from_slice(&m.to_be_bytes());
out.extend_from_slice(&ef_offset.to_be_bytes());
out.extend_from_slice(&BT_METADATA_LEN.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&BT_VERSION.to_be_bytes());
out.extend_from_slice(&FOOTER_MAGIC);
write_all(bt_path, &out)
}
}
}
fn pad_to(out: &mut Vec<u8>, align: usize) {
let rem = out.len() % align;
if rem != 0 {
out.resize(out.len() + (align - rem), 0);
}
}
fn write_all(path: &Path, bytes: &[u8]) -> Result<()> {
let mut f = File::create(path).map_err(|e| Error::io(path, e))?;
f.write_all(bytes).map_err(|e| Error::io(path, e))?;
f.flush().map_err(|e| Error::io(path, e))
}