use core::alloc::Layout;
use core::borrow::Borrow;
use core::error::Error;
use core::fmt;
use core::iter::FusedIterator;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut, RangeBounds};
use core::ptr::NonNull;
mod var_bytes;
pub use var_bytes::{VarBytes, VarBytesBuf};
use crate::allocator::Global;
use crate::inline_key::{Auto, InlineKeyStrategy};
use crate::tree::build::TreeBuilder;
use crate::tree::file::{FileFormat, FileHeader, FilePages, InFile};
use crate::tree::root::Tree;
use crate::unsync::BBTreeMap;
use crate::unsync::map::{Iter, Range};
pub unsafe trait PlainBytes: Copy + 'static {}
macro_rules! plain_bytes {
($($plain:ty),*) => {
$(unsafe impl PlainBytes for $plain {})*
};
}
plain_bytes!(
(),
u8,
u16,
u32,
u64,
u128,
usize,
i8,
i16,
i32,
i64,
i128,
isize,
f32,
f64
);
unsafe impl<T: PlainBytes, const N: usize> PlainBytes for [T; N] {}
pub trait Storable: storage::Storage {}
impl<T: storage::Storage> Storable for T {}
pub type Input<'a, T> = <T as storage::Storage>::Input<'a>;
pub type InWriter<T> = <T as storage::Storage>::InWriter;
pub(crate) mod storage {
pub use super::var_bytes::{ArenaBytes, ByteArena};
pub unsafe trait Storage: Sized {
type Input<'a>;
type InWriter: Copy;
const VAR_LEN: bool;
fn in_writer(input: Self::Input<'_>, arena: &mut ByteArena) -> Self::InWriter;
fn tail_bytes(in_writer: &Self::InWriter) -> &[u8];
}
unsafe impl<T: super::PlainBytes> Storage for T {
type Input<'a> = T;
type InWriter = T;
const VAR_LEN: bool = false;
fn in_writer(input: T, _arena: &mut ByteArena) -> T {
input
}
fn tail_bytes(_in_writer: &T) -> &[u8] {
&[]
}
}
}
pub struct FileWriter<K: Storable, V: Storable, S: InlineKeyStrategy<K::InWriter> = Auto> {
tree: Tree<K::InWriter, V::InWriter, S>,
length: usize,
_arena: storage::ByteArena,
}
impl<K, V, S> FileWriter<K, V, S>
where
K: Storable<InWriter: Ord>,
V: Storable,
S: InlineKeyStrategy<K::InWriter>,
{
pub fn from_ascending<'a>(
entries: impl IntoIterator<Item = (K::Input<'a>, V::Input<'a>)>,
) -> Result<Self, NotAscending> {
let mut arena = storage::ByteArena::default();
let built = (entries.into_iter()).map(|(key, value)| {
(
K::in_writer(key, &mut arena),
V::in_writer(value, &mut arena),
)
});
let mut entries = AscendingEntries {
entries: built,
previous: None,
count: 0,
out_of_order: false,
};
let (tree, length) = Self::build(&mut entries);
let out_of_order = entries.out_of_order;
let count = entries.count;
let writer = Self {
tree,
length,
_arena: arena,
};
if out_of_order {
return Err(NotAscending { index: count });
}
Ok(writer)
}
fn build(
entries: impl Iterator<Item = (K::InWriter, V::InWriter)>,
) -> (Tree<K::InWriter, V::InWriter, S>, usize) {
let mut builder = TreeBuilder::<K::InWriter, V::InWriter, S, Global>::new(Global);
builder.extend(entries);
builder.finish()
}
}
impl<K, V, S> FileWriter<K, V, S>
where
K: Storable,
V: Storable,
S: InlineKeyStrategy<K> + InlineKeyStrategy<K::InWriter>,
{
#[must_use]
pub fn len(&self) -> usize {
self.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.length == 0
}
#[must_use]
pub fn byte_len(&self) -> usize {
FilePages::<K, V, S>::of(&self.tree, self.length).byte_len()
}
pub fn write<E>(&self, mut write: impl FnMut(&[u8]) -> Result<(), E>) -> Result<(), E> {
FilePages::<K, V, S>::of(&self.tree, self.length).write(&mut write)
}
#[must_use]
pub fn to_buf(&self) -> FileBuf {
let file_bytes = FilePages::<K, V, S>::of(&self.tree, self.length);
let mut buffer = FileBuf::zeroed(file_bytes.byte_len(), FileFormat::<K, V, S>::alignment());
let mut written = 0;
file_bytes
.write(&mut |bytes: &[u8]| {
buffer[written..written + bytes.len()].copy_from_slice(bytes);
written += bytes.len();
Ok::<(), ()>(())
})
.expect("writing to memory doesn't fail");
buffer
}
}
impl<K: PlainBytes + Ord, V: PlainBytes, S: InlineKeyStrategy<K>> FileWriter<K, V, S> {
fn of_map_entries(entries: impl Iterator<Item = (K, V)>) -> Self {
let (tree, length) = Self::build(entries);
Self {
tree,
length,
_arena: storage::ByteArena::default(),
}
}
}
impl<K, V, S, A> From<&BBTreeMap<K, V, S, A>> for FileWriter<K, V, S>
where
K: PlainBytes + Ord,
V: PlainBytes,
S: InlineKeyStrategy<K>,
A: crate::Allocator + Clone,
{
fn from(map: &BBTreeMap<K, V, S, A>) -> Self {
Self::of_map_entries(map.iter().map(|(&key, &value)| (key, value)))
}
}
impl<K, V, S, A> From<&mut crate::BBTreeMap<K, V, S, A>> for FileWriter<K, V, S>
where
K: PlainBytes + Ord,
V: PlainBytes,
S: InlineKeyStrategy<K>,
A: crate::Allocator,
{
fn from(map: &mut crate::BBTreeMap<K, V, S, A>) -> Self {
Self::of_map_entries(map.iter_mut().map(|(&key, &mut value)| (key, value)))
}
}
impl<K: Storable, V: Storable, S: InlineKeyStrategy<K::InWriter>> Drop for FileWriter<K, V, S> {
fn drop(&mut self) {
self.tree.take().destroy(&Global);
}
}
struct AscendingEntries<I, K> {
entries: I,
previous: Option<K>,
count: usize,
out_of_order: bool,
}
impl<I: Iterator<Item = (K, V)>, K: Ord + Copy, V> Iterator for AscendingEntries<I, K> {
type Item = (K, V);
fn next(&mut self) -> Option<(K, V)> {
if self.out_of_order {
return None;
}
let (key, value) = self.entries.next()?;
if self.previous.is_some_and(|previous| previous >= key) {
self.out_of_order = true;
return None;
}
self.previous = Some(key);
self.count += 1;
Some((key, value))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NotAscending {
index: usize,
}
impl NotAscending {
#[must_use]
pub fn index(&self) -> usize {
self.index
}
}
impl fmt::Display for NotAscending {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"entry {} has a key not greater than the key before it",
self.index
)
}
}
impl Error for NotAscending {}
pub struct FileMap<'a, K, V, S = Auto> {
tree: Tree<K, V, InFile<S>>,
length: usize,
_bytes: PhantomData<&'a [u8]>,
}
impl<'a, K: Storable, V: Storable, S: InlineKeyStrategy<K>> FileMap<'a, K, V, S> {
#[must_use]
pub fn alignment() -> usize {
FileFormat::<K, V, S>::alignment()
}
pub fn open(file_bytes: &'a [u8]) -> Result<Self, FileError> {
let header = FileHeader::read::<K, V, S>(file_bytes)?;
FileFormat::<K, V, S>::check(file_bytes, header)?;
Ok(unsafe { Self::in_file(file_bytes, header) })
}
pub unsafe fn open_unchecked(file_bytes: &'a [u8]) -> Result<Self, FileError> {
let header = FileHeader::read::<K, V, S>(file_bytes)?;
Ok(unsafe { Self::in_file(file_bytes, header) })
}
unsafe fn in_file(file_bytes: &'a [u8], header: FileHeader) -> Self {
Self {
tree: unsafe { Tree::in_file(file_bytes, header) },
length: header.length(),
_bytes: PhantomData,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q> + Ord,
Q: Ord + ?Sized,
{
self.get_key_value(key).map(|(_, value)| value)
}
pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q> + Ord,
Q: Ord + ?Sized,
{
self.tree.find(key)
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q> + Ord,
Q: Ord + ?Sized,
{
self.get_key_value(key).is_some()
}
pub fn get_many<Q, const N: usize>(&self, keys: [&Q; N]) -> [Option<&V>; N]
where
K: Borrow<Q> + Ord,
Q: Ord + ?Sized,
{
self.tree.find_many(keys, self.length)
}
#[must_use]
pub fn first_key_value(&self) -> Option<(&K, &V)> {
self.tree.first_key_value()
}
#[must_use]
pub fn last_key_value(&self) -> Option<(&K, &V)> {
self.tree.last_key_value()
}
pub fn iter(
&self,
) -> impl DoubleEndedIterator<Item = (&K, &V)> + ExactSizeIterator + FusedIterator + Clone {
Iter::of_tree(&self.tree, self.length)
}
pub fn keys(
&self,
) -> impl DoubleEndedIterator<Item = &K> + ExactSizeIterator + FusedIterator + Clone {
self.iter().map(|(key, _)| key)
}
pub fn values(
&self,
) -> impl DoubleEndedIterator<Item = &V> + ExactSizeIterator + FusedIterator + Clone {
self.iter().map(|(_, value)| value)
}
pub fn range<T, R>(&self, range: R) -> FileMapRange<'_, K, V, S>
where
T: Ord + ?Sized,
K: Borrow<T> + Ord,
R: RangeBounds<T>,
{
FileMapRange {
range: Range::of_tree(&self.tree, range),
}
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct FileMapRange<'m, K, V, S = Auto> {
range: Range<'m, K, V, InFile<S>>,
}
impl<'m, K: 'm, V: 'm, S: InlineKeyStrategy<K>> Iterator for FileMapRange<'m, K, V, S> {
type Item = (&'m K, &'m V);
fn next(&mut self) -> Option<(&'m K, &'m V)> {
self.range.next()
}
fn fold<A, F>(self, accumulator: A, fold: F) -> A
where
F: FnMut(A, Self::Item) -> A,
{
self.range.fold(accumulator, fold)
}
}
impl<'m, K: 'm, V: 'm, S: InlineKeyStrategy<K>> DoubleEndedIterator for FileMapRange<'m, K, V, S> {
fn next_back(&mut self) -> Option<(&'m K, &'m V)> {
self.range.next_back()
}
fn rfold<A, F>(self, accumulator: A, fold: F) -> A
where
F: FnMut(A, Self::Item) -> A,
{
self.range.rfold(accumulator, fold)
}
}
impl<K, V, S: InlineKeyStrategy<K>> FusedIterator for FileMapRange<'_, K, V, S> {}
impl<K, V, S> Clone for FileMapRange<'_, K, V, S> {
fn clone(&self) -> Self {
Self {
range: self.range.clone(),
}
}
}
impl<K, V, S> fmt::Debug for FileMap<'_, K, V, S>
where
K: Storable + fmt::Debug,
V: Storable + fmt::Debug,
S: InlineKeyStrategy<K>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileError {
NotAFileMap,
UnsupportedVersion(u32),
LayoutMismatch,
Misaligned {
alignment: usize,
},
Truncated {
byte_len: usize,
available: usize,
},
Corrupt(&'static str),
}
impl fmt::Display for FileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotAFileMap => f.write_str("not a bbtree file map"),
Self::UnsupportedVersion(version) => {
write!(
f,
"a bbtree file map of unsupported format version {version}"
)
}
Self::LayoutMismatch => f.write_str("a bbtree file map of another layout"),
Self::Misaligned { alignment } => {
write!(f, "a bbtree file map not aligned to {alignment} bytes")
}
Self::Truncated {
byte_len,
available,
} => write!(
f,
"a bbtree file map of {byte_len} bytes, of which {available} are given"
),
Self::Corrupt(what) => write!(f, "a corrupt bbtree file map: {what}"),
}
}
}
impl Error for FileError {}
pub struct FileBuf {
bytes: NonNull<u8>,
len: usize,
layout: Layout,
}
unsafe impl Send for FileBuf {}
unsafe impl Sync for FileBuf {}
impl FileBuf {
#[must_use]
pub fn zeroed(len: usize, alignment: usize) -> Self {
let layout = Layout::from_size_align(len.max(1), alignment)
.expect("a file buffer's alignment is a power of two");
let bytes = crate::Allocator::allocate_zeroed(&Global, layout)
.unwrap_or_else(|_| alloc::alloc::handle_alloc_error(layout));
Self { bytes, len, layout }
}
}
impl Deref for FileBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.bytes.as_ptr(), self.len) }
}
}
impl DerefMut for FileBuf {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.bytes.as_ptr(), self.len) }
}
}
impl Drop for FileBuf {
fn drop(&mut self) {
unsafe { crate::Allocator::deallocate(&Global, self.bytes, self.layout) }
}
}
impl fmt::Debug for FileBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FileBuf")
.field("len", &self.len)
.field("alignment", &self.layout.align())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests;