Skip to content

Commit

Permalink
Implement Key and Value for [T;N]
Browse files Browse the repository at this point in the history
  • Loading branch information
cberner committed Mar 20, 2024
1 parent 1f5f52c commit b1c8a9d
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::cmp::Ordering;
use std::convert::TryInto;
use std::fmt::Debug;
use std::mem::size_of;

#[derive(Eq, PartialEq, Clone, Debug)]
enum TypeClassification {
Expand Down Expand Up @@ -353,6 +354,104 @@ impl<const N: usize> Key for &[u8; N] {
}
}

impl<const N: usize, T: Value> Value for [T; N] {
type SelfType<'a> = [T::SelfType<'a>; N]
where
Self: 'a;
type AsBytes<'a> = Vec<u8>
where
Self: 'a;

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

fn from_bytes<'a>(data: &'a [u8]) -> [T::SelfType<'a>; N]
where
Self: 'a,
{
let mut result = Vec::with_capacity(N);
if let Some(fixed) = T::fixed_width() {
for i in 0..N {
result.push(T::from_bytes(&data[fixed * i..fixed * (i + 1)]));
}
} else {
// Set offset to the first data item
let mut start = size_of::<u32>() * N;
for i in 0..N {
let range = size_of::<u32>() * i..size_of::<u32>() * (i + 1);
let end = start + u32::from_le_bytes(data[range].try_into().unwrap()) as usize;
let x = T::from_bytes(&data[start..end]);
result.push(x);
start = end;
}
}
result.try_into().unwrap()
}

fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Vec<u8>
where
Self: 'a,
Self: 'b,
{
if let Some(fixed) = T::fixed_width() {
let mut result = Vec::with_capacity(fixed * N);
for item in value.iter() {
result.extend_from_slice(T::as_bytes(item).as_ref());
}
result
} else {
// Reserve space for the lengths
let mut result = vec![0u8; size_of::<u32>() * N];
for i in 0..N {
let x = T::as_bytes(&value[i]);
result.extend_from_slice(x.as_ref());
let len: u32 = x.as_ref().len().try_into().unwrap();
result[size_of::<u32>() * i..size_of::<u32>() * (i + 1)]
.copy_from_slice(&len.to_le_bytes());
}
result
}
}

fn type_name() -> TypeName {
// Uses the same type name as [T;N] so that tables are compatible with [u8;N] and &[u8;N] types
// This requires that the binary encoding be the same
TypeName::internal(&format!("[{};{N}]", T::type_name().name()))
}
}

impl<const N: usize, T: Key> Key for [T; N] {
fn compare(data1: &[u8], data2: &[u8]) -> Ordering {
if let Some(fixed) = T::fixed_width() {
for i in 0..N {
let range = fixed * i..fixed * (i + 1);
let comparison = T::compare(&data1[range.clone()], &data2[range]);
if !comparison.is_eq() {
return comparison;
}
}
} else {
// Set offset to the first data item
let mut start1 = size_of::<u32>() * N;
let mut start2 = size_of::<u32>() * N;
for i in 0..N {
let range = size_of::<u32>() * i..size_of::<u32>() * (i + 1);
let end1 =
start1 + u32::from_le_bytes(data1[range.clone()].try_into().unwrap()) as usize;
let end2 = start2 + u32::from_le_bytes(data2[range].try_into().unwrap()) as usize;
let comparison = T::compare(&data1[start1..end1], &data2[start2..end2]);
if !comparison.is_eq() {
return comparison;
}
start1 = end1;
start2 = end2;
}
}
Ordering::Equal
}
}

impl Value for &str {
type SelfType<'a> = &'a str
where
Expand Down
82 changes: 82 additions & 0 deletions tests/basic_tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rand::random;
use redb::backends::InMemoryBackend;
use redb::{
Database, Key, MultimapTableDefinition, MultimapTableHandle, Range, ReadableTable,
Expand Down Expand Up @@ -728,6 +729,61 @@ fn tuple12_type() {
);
}

#[test]
#[allow(clippy::type_complexity)]
fn generic_array_type() {
let tmpfile = create_tempfile();
let db = Database::create(tmpfile.path()).unwrap();

let table_def1: TableDefinition<[u8; 3], [u64; 2]> = TableDefinition::new("table1");
let table_def2: TableDefinition<[(u8, &str); 2], [Option<u32>; 2]> =
TableDefinition::new("table2");
let table_def3: TableDefinition<[&[u8]; 2], [f32; 2]> = TableDefinition::new("table3");

let write_txn = db.begin_write().unwrap();
{
let mut table1 = write_txn.open_table(table_def1).unwrap();
let mut table2 = write_txn.open_table(table_def2).unwrap();
let mut table3 = write_txn.open_table(table_def3).unwrap();
table1.insert([0, 1, 2], &[4, 5]).unwrap();
table2
.insert([(0, "hi"), (1, "world")], [None, Some(2)])
.unwrap();
table3
.insert([b"hi".as_slice(), b"world".as_slice()], [4.0, 5.0])
.unwrap();
table3
.insert([b"longlong".as_slice(), b"longlong".as_slice()], [0.0, 0.0])
.unwrap();
table3
.insert([b"s".as_slice(), b"s".as_slice()], [0.0, 0.0])
.unwrap();
}
write_txn.commit().unwrap();

let read_txn = db.begin_read().unwrap();
let table1 = read_txn.open_table(table_def1).unwrap();
let table2 = read_txn.open_table(table_def2).unwrap();
let table3 = read_txn.open_table(table_def3).unwrap();
assert_eq!(table1.get(&[0, 1, 2]).unwrap().unwrap().value(), [4, 5]);
assert_eq!(
table2
.get(&[(0, "hi"), (1, "world")])
.unwrap()
.unwrap()
.value(),
[None, Some(2)]
);
assert_eq!(
table3
.get(&[b"hi".as_slice(), b"world".as_slice()])
.unwrap()
.unwrap()
.value(),
[4.0, 5.0]
);
}

#[test]
fn is_empty() {
let tmpfile = create_tempfile();
Expand Down Expand Up @@ -1625,3 +1681,29 @@ fn char_type() {
assert_eq!(iter.next().unwrap().unwrap().0.value(), 'b');
assert!(iter.next().is_none());
}

// Test that &[u8; N] and [u8; N] are effectively the same
#[test]
fn u8_array_serialization() {
assert_eq!(
<&[u8; 7] as Value>::type_name(),
<[u8; 7] as Value>::type_name()
);
let fixed_value: u128 = random();
let fixed_serialized = fixed_value.to_le_bytes();
for _ in 0..1000 {
let x: u128 = random();
let x_serialized = x.to_le_bytes();
let ref_x_serialized = &x_serialized;
let u8_ref_serialized = <&[u8; 16] as Value>::as_bytes(&ref_x_serialized);
let u8_generic_serialized = <[u8; 16] as Value>::as_bytes(&x_serialized);
assert_eq!(
u8_ref_serialized.as_slice(),
u8_generic_serialized.as_slice()
);
assert_eq!(u8_ref_serialized.as_slice(), x_serialized.as_slice());
let ref_order = <&[u8; 16] as Key>::compare(&x_serialized, &fixed_serialized);
let generic_order = <[u8; 16] as Key>::compare(&x_serialized, &fixed_serialized);
assert_eq!(ref_order, generic_order);
}
}

0 comments on commit b1c8a9d

Please sign in to comment.