Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option support and alignment future proofing #490

Merged
merged 2 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/tree_store/page_store/page_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
84 changes: 83 additions & 1 deletion src/tree_store/table_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ impl From<u8> 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<usize>,
fixed_value_size: Option<usize>,
key_alignment: usize,
value_alignment: usize,
key_type: String,
value_type: String,
}
Expand All @@ -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
}
Expand Down Expand Up @@ -192,6 +202,18 @@ impl RedbValue for InternalTableDefinition {
None
};
offset += size_of::<u32>();
let key_alignment = u32::from_le_bytes(
data[offset..(offset + size_of::<u32>())]
.try_into()
.unwrap(),
) as usize;
offset += size_of::<u32>();
let value_alignment = u32::from_le_bytes(
data[offset..(offset + size_of::<u32>())]
.try_into()
.unwrap(),
) as usize;
offset += size_of::<u32>();

let key_type_len = u32::from_le_bytes(
data[offset..(offset + size_of::<u32>())]
Expand All @@ -210,6 +232,8 @@ impl RedbValue for InternalTableDefinition {
table_type,
fixed_key_size,
fixed_value_size,
key_alignment,
value_alignment,
key_type,
value_type,
}
Expand Down Expand Up @@ -244,6 +268,8 @@ impl RedbValue for InternalTableDefinition {
result.push(0);
result.extend_from_slice(&[0; size_of::<u32>()])
}
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()
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
};
Expand Down Expand Up @@ -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);
}
}
47 changes: 47 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -73,6 +76,50 @@ impl RedbValue for () {

impl Sealed for () {}

impl<T: RedbValue> RedbValue for Option<T> {
const ALIGNMENT: usize = T::ALIGNMENT;
type SelfType<'a> = Option<T::SelfType<'a>>
where
Self: 'a;
type AsBytes<'a> = Vec<u8>
where
Self: 'a;

fn fixed_width() -> Option<usize> {
T::fixed_width().map(|x| x + T::ALIGNMENT)
}

fn from_bytes<'a>(data: &'a [u8]) -> Option<T::SelfType<'a>>
where
Self: 'a,
{
match data[0] {
0 => None,
1 => Some(T::from_bytes(&data[T::ALIGNMENT..])),
_ => unreachable!(),
}
}

fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Vec<u8>
where
Self: 'a,
Self: 'b,
{
let mut result = vec![0; T::ALIGNMENT];
if let Some(x) = value {
result[0] = 1;
result.extend_from_slice(T::as_bytes(x).as_ref());
}
result
}

fn redb_type_name() -> String {
format!("Option<{}>", T::redb_type_name())
}
}

impl<T: RedbValue> Sealed for Option<T> {}

impl RedbValue for &[u8] {
type SelfType<'a> = &'a [u8]
where
Expand Down
21 changes: 21 additions & 0 deletions tests/basic_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,27 @@ fn empty_type() {
assert!(!table.is_empty().unwrap());
}

#[test]
fn option_type() {
let tmpfile: NamedTempFile = NamedTempFile::new().unwrap();
let db = Database::create(tmpfile.path()).unwrap();

let definition: TableDefinition<u8, Option<u32>> = TableDefinition::new("x");

let write_txn = db.begin_write().unwrap();
{
let mut table = write_txn.open_table(definition).unwrap();
table.insert(0, None).unwrap();
table.insert(1, Some(1)).unwrap();
}
write_txn.commit().unwrap();

let read_txn = db.begin_read().unwrap();
let table = read_txn.open_table(definition).unwrap();
assert_eq!(table.get(0).unwrap().unwrap().value(), None);
assert_eq!(table.get(1).unwrap().unwrap().value(), Some(1));
}

#[test]
fn array_type() {
let tmpfile: NamedTempFile = NamedTempFile::new().unwrap();
Expand Down