mod audit;
mod cursor;
mod page;
mod search;
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests;
mod transaction;
pub use cursor::{Entry, Range};
use page::{Header, Node, Record};
use std::{cmp::Ordering, fmt, ops::Bound};
use transaction::Transaction;
use super::Memory;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Error {
NotEmpty,
Format,
Configuration,
Corrupt,
TooLarge,
Capacity,
InvalidKey,
InvalidRange,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "stable B-tree error: {self:?}")
}
}
impl std::error::Error for Error {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
pub max_key_bytes: u32,
pub max_value_bytes: u32,
}
impl Default for Config {
fn default() -> Self {
Self {
max_key_bytes: 128 * 1024,
max_value_bytes: 4 * 1024 * 1024,
}
}
}
impl Config {
fn check(self) -> Result<(), Error> {
if self.max_key_bytes == 0
|| self.max_key_bytes > 1024 * 1024
|| self.max_value_bytes == 0
|| self.max_value_bytes > 64 * 1024 * 1024
{
return Err(Error::Configuration);
}
Ok(())
}
}
pub trait Comparator {
const IDENTITY: [u8; 16];
fn compare(&self, left: &[u8], right: &[u8]) -> Result<Ordering, Error>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Lexicographic;
impl Comparator for Lexicographic {
const IDENTITY: [u8; 16] = *b"ic-storage-lex01";
fn compare(&self, left: &[u8], right: &[u8]) -> Result<Ordering, Error> {
Ok(left.cmp(right))
}
}
pub struct BTreeMap<M, C = Lexicographic> {
memory: M,
order: C,
header: Header,
}
impl<M: Memory, C: Comparator> BTreeMap<M, C> {
pub fn create(memory: M, config: Config, order: C) -> Result<Self, Error> {
config.check()?;
if memory.size() != 0 {
return Err(Error::NotEmpty);
}
let mut tree = Self {
memory,
order,
header: Header::new(config, C::IDENTITY),
};
let mut tx = Transaction::new(&tree);
let root = tx.allocate()?;
tx.put_node(root, &Node::leaf())?;
tx.header.root = root;
tree.header = tx.commit()?;
Ok(tree)
}
pub fn open(memory: M, config: Config, order: C) -> Result<Self, Error> {
config.check()?;
let header = Header::read(&memory)?;
if header.config != config || header.identity != C::IDENTITY {
return Err(Error::Configuration);
}
let tree = Self {
memory,
order,
header,
};
tree.node(tree.header.root)?;
Ok(tree)
}
#[must_use]
pub const fn len(&self) -> u64 {
self.header.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.header.len == 0
}
#[must_use]
pub fn memory_bytes(&self) -> u64 {
self.memory.size().saturating_mul(65_536)
}
#[must_use]
pub fn into_memory(self) -> M {
self.memory
}
pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
self.check_key(key)?;
self.find_validated(key)?
.map(|(_, node, at)| self.value(node.entries[at]))
.transpose()
}
pub fn contains_key(&self, key: &[u8]) -> Result<bool, Error> {
self.check_key(key)?;
Ok(self.find_validated(key)?.is_some())
}
fn find_validated(&self, key: &[u8]) -> Result<Option<(u64, Node, usize)>, Error> {
let mut id = self.header.root;
for _ in 0..page::MAX_HEIGHT {
let node = self.node(id)?;
let (at, found) = self.locate(&node, key)?;
if found {
return Ok(Some((id, node, at)));
}
if node.children.is_empty() {
return Ok(None);
}
id = node.children[at];
}
Err(Error::Corrupt)
}
pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<Option<Vec<u8>>, Error> {
self.check_key(key)?;
if value.len() > self.header.config.max_value_bytes as usize {
return Err(Error::TooLarge);
}
let located = self.find_validated(key)?;
let mut tx = Transaction::new(self);
let previous = if let Some((id, node, at)) = located {
Some(tx.replace(id, node, at, value)?)
} else {
tx.insert(key, value)?;
tx.header.len = tx.header.len.checked_add(1).ok_or(Error::Capacity)?;
None
};
self.header = tx.commit()?;
Ok(previous)
}
pub fn remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
self.check_key(key)?;
let mut tx = Transaction::new(self);
let Some(previous) = tx.remove(key)? else {
return Ok(None);
};
tx.header.len = tx.header.len.checked_sub(1).ok_or(Error::Corrupt)?;
self.header = tx.commit()?;
Ok(Some(previous))
}
pub fn range(
&self,
lower: Bound<&[u8]>,
upper: Bound<&[u8]>,
) -> Result<Range<'_, M, C>, Error> {
Range::new(self, lower, upper)
}
#[must_use]
pub fn iter(&self) -> Range<'_, M, C> {
Range::all(self)
}
pub fn validate(&self) -> Result<(), Error> {
audit::validate(self)
}
fn check_key(&self, key: &[u8]) -> Result<(), Error> {
if key.len() > self.header.config.max_key_bytes as usize {
return Err(Error::TooLarge);
}
if self.order.compare(key, key)? != Ordering::Equal {
return Err(Error::InvalidKey);
}
Ok(())
}
fn node(&self, id: u64) -> Result<Node, Error> {
let mut page = [0; page::SIZE];
page::read_into(&self.memory, self.header.next, id, &mut page)?;
Node::decode(&page, self.header.next, self.header.config)
}
fn key(&self, record: Record) -> Result<Vec<u8>, Error> {
if record.key == 0 {
return Ok(record.key_inline
[..usize::try_from(record.key_len).map_err(|_| Error::Corrupt)?]
.to_vec());
}
page::blob(&self.memory, self.header.next, record.key, record.key_len)
}
fn value(&self, record: Record) -> Result<Vec<u8>, Error> {
if record.value == 0 {
return Ok(record.value_inline
[..usize::try_from(record.value_len).map_err(|_| Error::Corrupt)?]
.to_vec());
}
page::blob(
&self.memory,
self.header.next,
record.value,
record.value_len,
)
}
fn locate(&self, node: &Node, key: &[u8]) -> Result<(usize, bool), Error> {
search::locate(
node,
key,
&self.order,
|id, buffer| page::read_into(&self.memory, self.header.next, id, buffer),
|record| self.key(record),
)
}
}
impl<'a, M: Memory, C: Comparator> IntoIterator for &'a BTreeMap<M, C> {
type Item = Result<Entry<'a, M, C>, Error>;
type IntoIter = Range<'a, M, C>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}