use std::{error::Error, fmt};
const ROW_KEY_TAG: u8 = 0x01;
const TABLE_PREFIX_LEN: usize = 1 + std::mem::size_of::<u32>();
const ROW_KEY_LEN: usize = TABLE_PREFIX_LEN + std::mem::size_of::<u64>();
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RowKeyRangeError {
InvalidKeyEncoding,
InvalidBounds,
CrossTableBounds,
}
impl fmt::Display for RowKeyRangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidKeyEncoding => f.write_str("invalid canonical primary row-key encoding"),
Self::InvalidBounds => {
f.write_str("row-key range bounds are not a non-empty half-open interval")
}
Self::CrossTableBounds => f.write_str("row-key range bounds refer to different tables"),
}
}
}
impl Error for RowKeyRangeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CanonicalRowKey {
table_id: u32,
row_id: u64,
}
impl CanonicalRowKey {
pub const fn new(table_id: u32, row_id: u64) -> Self {
Self { table_id, row_id }
}
pub fn decode(encoded: &[u8]) -> Result<Self, RowKeyRangeError> {
if encoded.len() != ROW_KEY_LEN || encoded[0] != ROW_KEY_TAG {
return Err(RowKeyRangeError::InvalidKeyEncoding);
}
let table_id = u32::from_be_bytes(
encoded[1..TABLE_PREFIX_LEN]
.try_into()
.map_err(|_| RowKeyRangeError::InvalidKeyEncoding)?,
);
let row_id = u64::from_be_bytes(
encoded[TABLE_PREFIX_LEN..]
.try_into()
.map_err(|_| RowKeyRangeError::InvalidKeyEncoding)?,
);
Ok(Self::new(table_id, row_id))
}
pub fn encode(self) -> Vec<u8> {
let mut encoded = Vec::with_capacity(ROW_KEY_LEN);
encoded.extend_from_slice(&Self::table_prefix(self.table_id));
encoded.extend_from_slice(&self.row_id.to_be_bytes());
encoded
}
pub const fn table_id(self) -> u32 {
self.table_id
}
pub const fn row_id(self) -> u64 {
self.row_id
}
pub fn table_prefix(table_id: u32) -> Vec<u8> {
let mut prefix = Vec::with_capacity(TABLE_PREFIX_LEN);
prefix.push(ROW_KEY_TAG);
prefix.extend_from_slice(&table_id.to_be_bytes());
prefix
}
pub fn table_prefix_end(table_id: u32) -> Vec<u8> {
prefix_successor(&Self::table_prefix(table_id))
.expect("a primary row-key prefix beginning with 0x01 always has a successor")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncodedRowKeyRange {
pub lower_inclusive: Vec<u8>,
pub upper_exclusive: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RowKeyRange {
table_id: u32,
lower_inclusive: Option<u64>,
upper_exclusive: Option<u64>,
}
impl RowKeyRange {
pub const fn full_table(table_id: u32) -> Self {
Self {
table_id,
lower_inclusive: None,
upper_exclusive: None,
}
}
pub fn new(
table_id: u32,
lower_inclusive: Option<u64>,
upper_exclusive: Option<u64>,
) -> Result<Self, RowKeyRangeError> {
if let (Some(lower), Some(upper)) = (lower_inclusive, upper_exclusive) {
if lower >= upper {
return Err(RowKeyRangeError::InvalidBounds);
}
}
Ok(Self {
table_id,
lower_inclusive,
upper_exclusive,
})
}
pub fn from_keys(
lower_inclusive: Option<CanonicalRowKey>,
upper_exclusive: Option<CanonicalRowKey>,
table_id: u32,
) -> Result<Self, RowKeyRangeError> {
for key in [lower_inclusive, upper_exclusive].into_iter().flatten() {
if key.table_id() != table_id {
return Err(RowKeyRangeError::CrossTableBounds);
}
}
Self::new(
table_id,
lower_inclusive.map(CanonicalRowKey::row_id),
upper_exclusive.map(CanonicalRowKey::row_id),
)
}
pub const fn table_id(self) -> u32 {
self.table_id
}
pub const fn lower_inclusive(self) -> Option<u64> {
self.lower_inclusive
}
pub const fn upper_exclusive(self) -> Option<u64> {
self.upper_exclusive
}
pub fn encoded_bounds(self) -> EncodedRowKeyRange {
EncodedRowKeyRange {
lower_inclusive: CanonicalRowKey::new(self.table_id, self.lower_inclusive.unwrap_or(0))
.encode(),
upper_exclusive: self.upper_exclusive.map_or_else(
|| CanonicalRowKey::table_prefix_end(self.table_id),
|row_id| CanonicalRowKey::new(self.table_id, row_id).encode(),
),
}
}
pub fn contains(self, key: CanonicalRowKey) -> bool {
key.table_id() == self.table_id
&& self
.lower_inclusive
.is_none_or(|lower| key.row_id() >= lower)
&& self
.upper_exclusive
.is_none_or(|upper| key.row_id() < upper)
}
}
fn prefix_successor(prefix: &[u8]) -> Option<Vec<u8>> {
let mut successor = prefix.to_vec();
for index in (0..successor.len()).rev() {
if successor[index] != u8::MAX {
successor[index] += 1;
successor.truncate(index + 1);
return Some(successor);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_key_roundtrip_and_lexicographic_order_are_stable() {
let first = CanonicalRowKey::new(7, 1);
let later = CanonicalRowKey::new(7, u64::MAX);
assert_eq!(CanonicalRowKey::decode(&first.encode()).unwrap(), first);
assert!(first.encode() < later.encode());
}
#[test]
fn full_table_range_uses_prefix_successor_for_the_upper_bound() {
let range = RowKeyRange::full_table(u32::MAX);
let encoded = range.encoded_bounds();
assert_eq!(
encoded.lower_inclusive,
CanonicalRowKey::new(u32::MAX, 0).encode()
);
assert_eq!(encoded.upper_exclusive, vec![0x02]);
assert!(range.contains(CanonicalRowKey::new(u32::MAX, u64::MAX)));
}
#[test]
fn half_open_bounds_include_lower_and_exclude_upper() {
let range = RowKeyRange::new(3, Some(10), Some(20)).unwrap();
assert!(!range.contains(CanonicalRowKey::new(3, 9)));
assert!(range.contains(CanonicalRowKey::new(3, 10)));
assert!(range.contains(CanonicalRowKey::new(3, 19)));
assert!(!range.contains(CanonicalRowKey::new(3, 20)));
assert!(!range.contains(CanonicalRowKey::new(4, 10)));
}
#[test]
fn invalid_or_cross_table_bounds_are_rejected() {
assert_eq!(
CanonicalRowKey::decode(&[0x02, 0, 0, 0, 1]),
Err(RowKeyRangeError::InvalidKeyEncoding)
);
assert_eq!(
RowKeyRange::new(3, Some(20), Some(10)),
Err(RowKeyRangeError::InvalidBounds)
);
assert_eq!(
RowKeyRange::from_keys(Some(CanonicalRowKey::new(4, 1)), None, 3),
Err(RowKeyRangeError::CrossTableBounds)
);
}
}