cuprate_database/key.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
//! Database key abstraction; `trait Key`.
//---------------------------------------------------------------------------------------------------- Import
use std::{cmp::Ordering, fmt::Debug};
use crate::{storable::Storable, StorableBytes, StorableStr, StorableVec};
//---------------------------------------------------------------------------------------------------- Table
/// Database [`Table`](crate::table::Table) key metadata.
///
/// Purely compile time information for database table keys.
///
/// ## Comparison
/// There are 2 differences between [`Key`] and [`Storable`]:
/// 1. [`Key`] must be [`Sized`]
/// 2. [`Key`] represents a [`Storable`] type that defines a comparison function
///
/// The database backends will use [`Key::KEY_COMPARE`]
/// to sort the keys within database tables.
///
/// [`Key::KEY_COMPARE`] is pre-implemented as a straight byte comparison.
///
/// This default is overridden for numbers, which use a number comparison.
/// For example, [`u64`] keys are sorted as `{0, 1, 2 ... 999_998, 999_999, 1_000_000}`.
///
/// If you would like to re-define this for number types, consider;
/// 1. Creating a wrapper type around primitives like a `struct SortU8(pub u8)`
/// 2. Implement [`Key`] on that wrapper
/// 3. Define a custom [`Key::KEY_COMPARE`]
pub trait Key: Storable + Sized + Ord {
/// Compare 2 [`Key`]'s against each other.
///
/// # Defaults for types
/// For arrays and vectors that contain a `T: Storable`,
/// this does a straight _byte_ comparison, not a comparison of the key's value.
///
/// For [`StorableStr`], this will use [`str::cmp`], i.e. it is the same as the default behavior; it is a
/// [lexicographical comparison](https://doc.rust-lang.org/std/cmp/trait.Ord.html#lexicographical-comparison)
///
/// For all primitive number types ([`u8`], [`i128`], etc), this will
/// convert the bytes to the number using [`Storable::from_bytes`],
/// then do a number comparison.
///
/// # Example
/// ```rust
/// # use cuprate_database::*;
/// // Normal byte comparison.
/// let vec1 = StorableVec(vec![0, 1]);
/// let vec2 = StorableVec(vec![255, 0]);
/// assert_eq!(
/// <StorableVec<u8> as Key>::KEY_COMPARE
/// .as_compare_fn::<StorableVec<u8>>()(&vec1, &vec2),
/// std::cmp::Ordering::Less,
/// );
///
/// // Integer comparison.
/// let byte1 = [0, 1]; // 256
/// let byte2 = [255, 0]; // 255
/// let num1 = u16::from_le_bytes(byte1);
/// let num2 = u16::from_le_bytes(byte2);
/// assert_eq!(num1, 256);
/// assert_eq!(num2, 255);
/// assert_eq!(
/// // 256 > 255
/// <u16 as Key>::KEY_COMPARE.as_compare_fn::<u16>()(&byte1, &byte2),
/// std::cmp::Ordering::Greater,
/// );
/// ```
const KEY_COMPARE: KeyCompare = KeyCompare::Default;
}
//---------------------------------------------------------------------------------------------------- Impl
/// [`Ord`] comparison for arrays/vectors.
impl<const N: usize, T> Key for [T; N] where T: Key + Storable + Sized + bytemuck::Pod {}
impl<T: bytemuck::Pod + Debug + Ord> Key for StorableVec<T> {}
/// [`Ord`] comparison for misc types.
///
/// This is not a blanket implementation because
/// it allows outer crates to define their own
/// comparison functions for their `T: Storable` types.
impl Key for () {}
impl Key for StorableBytes {}
impl Key for StorableStr {}
/// Number comparison.
///
/// # Invariant
/// This must _only_ be implemented for [`u32`], [`u64`] (and maybe [`usize`]).
///
/// This is because:
/// 1. We use LMDB's `INTEGER_KEY` flag when this enum variant is used
/// 2. LMDB only supports these types when using that flag
///
/// See: <https://docs.rs/heed/0.20.0-alpha.9/heed/struct.DatabaseFlags.html#associatedconstant.INTEGER_KEY>
///
/// Other numbers will still have the same behavior, but they use
/// [`impl_custom_numbers_key`] and essentially pass LMDB a "custom"
/// number compare function.
macro_rules! impl_number_key {
($($t:ident),* $(,)?) => {
$(
impl Key for $t {
const KEY_COMPARE: KeyCompare = KeyCompare::Number;
}
)*
};
}
impl_number_key!(u32, u64, usize);
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("`cuprate_database`: `usize` must be equal to `u32` or `u64` for LMDB's `usize` key sorting to function correctly");
/// Custom number comparison for other numbers.
macro_rules! impl_custom_numbers_key {
($($t:ident),* $(,)?) => {
$(
impl Key for $t {
// Just forward the the number comparison function.
const KEY_COMPARE: KeyCompare = KeyCompare::Custom(|left, right| {
KeyCompare::Number.as_compare_fn::<$t>()(left, right)
});
}
)*
};
}
impl_custom_numbers_key!(u8, u16, u128, i8, i16, i32, i64, i128, isize);
//---------------------------------------------------------------------------------------------------- KeyCompare
/// Comparison behavior for [`Key`]s.
///
/// This determines how the database sorts [`Key`]s inside a database [`Table`](crate::Table).
///
/// See [`Key`] for more info.
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum KeyCompare {
/// Use the default comparison behavior of the backend.
///
/// Currently, both `heed` and `redb` use
/// [lexicographical comparison](https://doc.rust-lang.org/1.79.0/std/cmp/trait.Ord.html#lexicographical-comparison)
/// by default, i.e. a straight byte comparison.
#[default]
Default,
/// A by-value number comparison, i.e. `255 < 256`.
///
/// This _behavior_ is implemented as the default for all number primitives,
/// although some implementations on numbers use [`KeyCompare::Custom`] due
/// to internal implementation details of LMDB.
Number,
/// A custom sorting function.
///
/// The input of the function is 2 [`Key`]s in byte form.
Custom(fn(&[u8], &[u8]) -> Ordering),
}
impl KeyCompare {
/// Return [`Self`] as a pure comparison function.
///
/// The returned function expects 2 [`Key`]s in byte form as input.
#[inline]
pub const fn as_compare_fn<K: Key>(self) -> fn(&[u8], &[u8]) -> Ordering {
match self {
Self::Default => Ord::cmp,
Self::Number => |left, right| {
let left = <K as Storable>::from_bytes(left);
let right = <K as Storable>::from_bytes(right);
Ord::cmp(&left, &right)
},
Self::Custom(f) => f,
}
}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}