From 445a19be4f3eb9abf8051683302a4da80c0164d0 Mon Sep 17 00:00:00 2001 From: Christopher Berner Date: Sat, 14 Jan 2023 17:15:25 -0800 Subject: [PATCH] Add type alignment to file format --- src/db.rs | 8 ++- src/tree_store/page_store/page_manager.rs | 2 +- src/tree_store/table_tree.rs | 84 ++++++++++++++++++++++- src/types.rs | 14 ++-- 4 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/db.rs b/src/db.rs index 5b2cdf81..88eee78d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -51,9 +51,12 @@ impl<'a, K: RedbKey + ?Sized, V: RedbValue + ?Sized> TableDefinition<'a, K, V> { /// /// ## Invariant /// - /// `name` shall not be empty. + /// `name` must not be empty. pub const fn new(name: &'a str) -> Self { assert!(!name.is_empty()); + // Custom alignment is not currently supported + assert!(K::ALIGNMENT == 1); + assert!(V::ALIGNMENT == 1); Self { name, _key_type: PhantomData, @@ -102,6 +105,9 @@ pub struct MultimapTableDefinition<'a, K: RedbKey + ?Sized, V: RedbKey + ?Sized> impl<'a, K: RedbKey + ?Sized, V: RedbKey + ?Sized> MultimapTableDefinition<'a, K, V> { pub const fn new(name: &'a str) -> Self { assert!(!name.is_empty()); + // Custom alignment is not currently supported + assert!(K::ALIGNMENT == 1); + assert!(V::ALIGNMENT == 1); Self { name, _key_type: PhantomData, diff --git a/src/tree_store/page_store/page_manager.rs b/src/tree_store/page_store/page_manager.rs index b367b461..fcf81816 100644 --- a/src/tree_store/page_store/page_manager.rs +++ b/src/tree_store/page_store/page_manager.rs @@ -39,7 +39,7 @@ const MIN_DESIRED_USABLE_BYTES: usize = 1024 * 1024; const NUM_REGIONS: u32 = 1000; // TODO: set to 1, when version 1.0 is released -pub(crate) const FILE_FORMAT_VERSION: u8 = 107; +pub(crate) const FILE_FORMAT_VERSION: u8 = 108; fn ceil_log2(x: usize) -> usize { if x.is_power_of_two() { diff --git a/src/tree_store/table_tree.rs b/src/tree_store/table_tree.rs index e8470ba5..b6f6da56 100644 --- a/src/tree_store/table_tree.rs +++ b/src/tree_store/table_tree.rs @@ -98,12 +98,14 @@ impl From for TableType { } } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub(crate) struct InternalTableDefinition { table_root: Option<(PageNumber, Checksum)>, table_type: TableType, fixed_key_size: Option, fixed_value_size: Option, + key_alignment: usize, + value_alignment: usize, key_type: String, value_type: String, } @@ -121,6 +123,14 @@ impl InternalTableDefinition { self.fixed_value_size } + pub(crate) fn get_key_alignment(&self) -> usize { + self.key_alignment + } + + pub(crate) fn get_value_alignment(&self) -> usize { + self.value_alignment + } + pub(crate) fn get_type(&self) -> TableType { self.table_type } @@ -192,6 +202,18 @@ impl RedbValue for InternalTableDefinition { None }; offset += size_of::(); + let key_alignment = u32::from_le_bytes( + data[offset..(offset + size_of::())] + .try_into() + .unwrap(), + ) as usize; + offset += size_of::(); + let value_alignment = u32::from_le_bytes( + data[offset..(offset + size_of::())] + .try_into() + .unwrap(), + ) as usize; + offset += size_of::(); let key_type_len = u32::from_le_bytes( data[offset..(offset + size_of::())] @@ -210,6 +232,8 @@ impl RedbValue for InternalTableDefinition { table_type, fixed_key_size, fixed_value_size, + key_alignment, + value_alignment, key_type, value_type, } @@ -244,6 +268,8 @@ impl RedbValue for InternalTableDefinition { result.push(0); result.extend_from_slice(&[0; size_of::()]) } + result.extend_from_slice(&u32::try_from(value.key_alignment).unwrap().to_le_bytes()); + result.extend_from_slice(&u32::try_from(value.value_alignment).unwrap().to_le_bytes()); result.extend_from_slice( &u32::try_from(value.key_type.as_bytes().len()) .unwrap() @@ -370,6 +396,38 @@ impl<'txn> TableTree<'txn> { V::redb_type_name() ))); } + if definition.get_key_alignment() != K::ALIGNMENT { + return Err(Error::Corrupted(format!( + "{:?} key alignment {} does not match {}", + name, + K::ALIGNMENT, + definition.key_alignment + ))); + } + if definition.get_value_alignment() != V::ALIGNMENT { + return Err(Error::Corrupted(format!( + "{:?} value alignment {} does not match {}", + name, + V::ALIGNMENT, + definition.value_alignment + ))); + } + if definition.get_fixed_key_size() != K::fixed_width() { + return Err(Error::Corrupted(format!( + "{:?} key width {:?} does not match {:?}", + name, + K::fixed_width(), + definition.get_fixed_key_size() + ))); + } + if definition.get_fixed_value_size() != V::fixed_width() { + return Err(Error::Corrupted(format!( + "{:?} value width {:?} does not match {:?}", + name, + V::fixed_width(), + definition.get_fixed_value_size() + ))); + } if let Some(updated_root) = self.pending_table_updates.get(name) { definition.table_root = *updated_root; @@ -427,6 +485,8 @@ impl<'txn> TableTree<'txn> { table_type, fixed_key_size: K::fixed_width(), fixed_value_size: V::fixed_width(), + key_alignment: K::ALIGNMENT, + value_alignment: V::ALIGNMENT, key_type: K::redb_type_name(), value_type: V::redb_type_name(), }; @@ -480,3 +540,25 @@ impl<'txn> TableTree<'txn> { }) } } + +#[cfg(test)] +mod test { + use crate::tree_store::{InternalTableDefinition, TableType}; + use crate::RedbValue; + + #[test] + fn round_trip() { + let x = InternalTableDefinition { + table_root: None, + table_type: TableType::Multimap, + fixed_key_size: None, + fixed_value_size: Some(5), + key_alignment: 6, + value_alignment: 7, + key_type: "Key".to_string(), + value_type: "Value".to_string(), + }; + let y = InternalTableDefinition::from_bytes(InternalTableDefinition::as_bytes(&x).as_ref()); + assert_eq!(x, y); + } +} diff --git a/src/types.rs b/src/types.rs index 0702d42f..57b82b4f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -5,6 +5,8 @@ use std::fmt::Debug; pub trait Sealed {} pub trait RedbValue: Debug + Sealed { + const ALIGNMENT: usize = 1; + /// SelfType<'a> must be the same type as Self with all lifetimes replaced with 'a type SelfType<'a>: Debug + 'a where @@ -19,6 +21,7 @@ pub trait RedbValue: Debug + Sealed { /// Deserializes data /// Implementations may return a view over data, or an owned type + // TODO: implement guarantee that data is aligned to Self::ALIGNMENT fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> where Self: 'a; @@ -74,6 +77,7 @@ impl RedbValue for () { impl Sealed for () {} impl RedbValue for Option { + const ALIGNMENT: usize = T::ALIGNMENT; type SelfType<'a> = Option> where Self: 'a; @@ -82,7 +86,7 @@ impl RedbValue for Option { Self: 'a; fn fixed_width() -> Option { - T::fixed_width().map(|x| x + 1) + T::fixed_width().map(|x| x + T::ALIGNMENT) } fn from_bytes<'a>(data: &'a [u8]) -> Option> @@ -91,7 +95,7 @@ impl RedbValue for Option { { match data[0] { 0 => None, - 1 => Some(T::from_bytes(&data[1..])), + 1 => Some(T::from_bytes(&data[T::ALIGNMENT..])), _ => unreachable!(), } } @@ -101,12 +105,10 @@ impl RedbValue for Option { Self: 'a, Self: 'b, { - let mut result = vec![]; + let mut result = vec![0; T::ALIGNMENT]; if let Some(x) = value { - result.push(1); + result[0] = 1; result.extend_from_slice(T::as_bytes(x).as_ref()); - } else { - result.push(0); } result }