use std::{fmt, ops::Bound};
use bytes::Bytes;
use crate::storage::{Precondition, StorageError};
pub const MAX_SCAN_PAGE_ROWS: usize = 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SpaceId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ValueSemantics {
Mutable,
Immutable,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ValueIntegrity {
#[default]
BackendVerified,
ContentAddressed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StorageSpace {
pub id: SpaceId,
pub name: &'static str,
pub value_semantics: ValueSemantics,
pub value_integrity: ValueIntegrity,
}
impl StorageSpace {
pub(crate) const fn declare(
id: SpaceId,
name: &'static str,
value_semantics: ValueSemantics,
) -> Self {
Self {
id,
name,
value_semantics,
value_integrity: ValueIntegrity::BackendVerified,
}
}
pub(crate) const fn declare_content_addressed(
id: SpaceId,
name: &'static str,
value_semantics: ValueSemantics,
) -> Self {
Self {
id,
name,
value_semantics,
value_integrity: ValueIntegrity::ContentAddressed,
}
}
pub const fn mutable(id: SpaceId, name: &'static str) -> Self {
assert!(
crate::storage_spaces::may_declare(id, ValueSemantics::Mutable),
"this space id is registered immutable in ALL_STORAGE_SPACES; read \
the space back from the registry instead of re-declaring it"
);
Self::declare(id, name, ValueSemantics::Mutable)
}
pub const fn immutable(id: SpaceId, name: &'static str) -> Self {
assert!(
crate::storage_spaces::may_declare(id, ValueSemantics::Immutable),
"this space id is registered mutable in ALL_STORAGE_SPACES; read \
the space back from the registry instead of re-declaring it"
);
Self::declare(id, name, ValueSemantics::Immutable)
}
#[cfg(test)]
pub(crate) const fn mutable_view_for_corruption_test(self) -> Self {
Self {
id: self.id,
name: self.name,
value_semantics: ValueSemantics::Mutable,
value_integrity: ValueIntegrity::BackendVerified,
}
}
}
impl fmt::Display for StorageSpace {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{}({:?}, {:?})",
self.name, self.id, self.value_semantics
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Key(pub Bytes);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReadEntry {
pub key: Key,
pub value: ProjectedValue,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PutEntry {
pub key: Key,
pub value: StoredValue,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PutBatch {
pub entries: Vec<PutEntry>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BufferRange {
offset: usize,
length: usize,
}
impl BufferRange {
pub const fn new(offset: usize, length: usize) -> Self {
Self { offset, length }
}
pub const fn offset(self) -> usize {
self.offset
}
pub const fn len(self) -> usize {
self.length
}
pub const fn is_empty(self) -> bool {
self.length == 0
}
fn checked_end(self) -> Option<usize> {
self.offset.checked_add(self.length)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EncodedPut {
pub key: BufferRange,
pub value: BufferRange,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EncodedMutationBatch {
key_bytes: Bytes,
value_bytes: Bytes,
puts: Vec<EncodedPut>,
deletes: Vec<BufferRange>,
}
impl EncodedMutationBatch {
pub fn try_new(
key_bytes: Bytes,
value_bytes: Bytes,
puts: Vec<EncodedPut>,
deletes: Vec<BufferRange>,
) -> Result<Self, EncodedMutationBatchError> {
for (index, put) in puts.iter().enumerate() {
validate_buffer_range(put.key, key_bytes.len()).map_err(|()| {
EncodedMutationBatchError::PutKeyOutOfBounds {
index,
range: put.key,
buffer_len: key_bytes.len(),
}
})?;
validate_buffer_range(put.value, value_bytes.len()).map_err(|()| {
EncodedMutationBatchError::PutValueOutOfBounds {
index,
range: put.value,
buffer_len: value_bytes.len(),
}
})?;
}
for (index, range) in deletes.iter().copied().enumerate() {
validate_buffer_range(range, key_bytes.len()).map_err(|()| {
EncodedMutationBatchError::DeleteKeyOutOfBounds {
index,
range,
buffer_len: key_bytes.len(),
}
})?;
}
Ok(Self {
key_bytes,
value_bytes,
puts,
deletes,
})
}
pub fn put_count(&self) -> usize {
self.puts.len()
}
pub fn delete_count(&self) -> usize {
self.deletes.len()
}
pub fn is_empty(&self) -> bool {
self.puts.is_empty() && self.deletes.is_empty()
}
pub fn key_bytes(&self) -> &Bytes {
&self.key_bytes
}
pub fn value_bytes(&self) -> &Bytes {
&self.value_bytes
}
pub fn puts(&self) -> &[EncodedPut] {
&self.puts
}
pub fn deletes(&self) -> &[BufferRange] {
&self.deletes
}
pub(crate) fn into_parts(self) -> (Bytes, Bytes, Vec<EncodedPut>, Vec<BufferRange>) {
(self.key_bytes, self.value_bytes, self.puts, self.deletes)
}
}
fn validate_buffer_range(range: BufferRange, buffer_len: usize) -> Result<(), ()> {
match range.checked_end() {
Some(end) if end <= buffer_len => Ok(()),
Some(_) | None => Err(()),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EncodedMutationBatchError {
PutKeyOutOfBounds {
index: usize,
range: BufferRange,
buffer_len: usize,
},
PutValueOutOfBounds {
index: usize,
range: BufferRange,
buffer_len: usize,
},
DeleteKeyOutOfBounds {
index: usize,
range: BufferRange,
buffer_len: usize,
},
}
impl fmt::Display for EncodedMutationBatchError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PutKeyOutOfBounds {
index,
range,
buffer_len,
} => write!(
formatter,
"encoded put {index} key range {range:?} exceeds key buffer length {buffer_len}"
),
Self::PutValueOutOfBounds {
index,
range,
buffer_len,
} => write!(
formatter,
"encoded put {index} value range {range:?} exceeds value buffer length {buffer_len}"
),
Self::DeleteKeyOutOfBounds {
index,
range,
buffer_len,
} => write!(
formatter,
"encoded delete {index} key range {range:?} exceeds key buffer length {buffer_len}"
),
}
}
}
impl std::error::Error for EncodedMutationBatchError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoredValue {
pub bytes: Bytes,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyRange {
pub lower: Bound<Key>,
pub upper: Bound<Key>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Prefix {
pub bytes: Bytes,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GetOptions {
pub projection: CoreProjection,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ScanOrder {
#[default]
Ascending,
Descending,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BeginScanOptions {
pub projection: CoreProjection,
pub order: ScanOrder,
}
impl Default for BeginScanOptions {
fn default() -> Self {
Self {
projection: CoreProjection::FullValue,
order: ScanOrder::Ascending,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use = "a scan page carries `has_more`; dropping it silently truncates the scan"]
pub struct ScanChunk {
entries: Vec<ReadEntry>,
has_more: bool,
}
impl ScanChunk {
pub fn new(entries: Vec<ReadEntry>, has_more: bool) -> Self {
Self { entries, has_more }
}
pub fn into_parts(self) -> (Vec<ReadEntry>, bool) {
(self.entries, self.has_more)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GetManyResult {
pub values: Vec<Option<ProjectedValue>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GetManyRequest<'a> {
pub space: StorageSpace,
pub keys: &'a [Key],
pub opts: GetOptions,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CoreProjection {
KeyOnly,
FullValue,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProjectedValue {
KeyOnly,
FullValue(Bytes),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ReadOptions {
pub snapshot: Option<SnapshotRef>,
pub consistency: ReadConsistency,
pub durability: ReadDurability,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ReadDurability {
#[default]
Visible,
Durable,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ReadConsistency {
#[default]
Snapshot,
StaleOk,
Latest,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct WriteOptions {
pub base_snapshot: Option<SnapshotRef>,
pub idempotency_key: Option<Bytes>,
pub await_durable: bool,
pub preconditions: Vec<Precondition>,
pub batch_capacity_hint_bytes: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SnapshotRef(pub Bytes);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct WriteStats {
pub put_entries: u64,
pub deleted_entries: u64,
pub deleted_ranges: u64,
pub written_bytes: u64,
pub storage_calls: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CommitResult {
pub commit_id: Option<Bytes>,
pub stats: WriteStats,
}
impl Prefix {
pub fn to_range(&self) -> Result<KeyRange, StorageError> {
let lower = Key(self.bytes.clone());
let mut upper = self.bytes.to_vec();
while let Some(last) = upper.last_mut() {
if *last == u8::MAX {
upper.pop();
} else {
*last += 1;
return Ok(KeyRange {
lower: Bound::Included(lower),
upper: Bound::Excluded(Key(Bytes::from(upper))),
});
}
}
Ok(KeyRange {
lower: Bound::Included(lower),
upper: Bound::Unbounded,
})
}
}
impl Default for GetOptions {
fn default() -> Self {
Self {
projection: CoreProjection::FullValue,
}
}
}
impl GetManyResult {
pub fn new(values: Vec<Option<ProjectedValue>>) -> Self {
Self { values }
}
pub fn entries_for_requested_keys(&self, keys: &[Key]) -> Vec<ReadEntry> {
keys.iter()
.cloned()
.zip(self.values.iter().cloned())
.filter_map(|(key, value)| value.map(|value| ReadEntry { key, value }))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefix_ranges_cover_exact_lexicographic_edges() {
let cases = [
(
&[][..],
Bound::Included(Key(Bytes::new())),
Bound::Unbounded,
),
(
&[0x61][..],
Bound::Included(Key(Bytes::from_static(&[0x61]))),
Bound::Excluded(Key(Bytes::from_static(&[0x62]))),
),
(
&[0x61, 0xff][..],
Bound::Included(Key(Bytes::from_static(&[0x61, 0xff]))),
Bound::Excluded(Key(Bytes::from_static(&[0x62]))),
),
(
&[0xff, 0xff][..],
Bound::Included(Key(Bytes::from_static(&[0xff, 0xff]))),
Bound::Unbounded,
),
];
for (bytes, lower, upper) in cases {
let actual = Prefix {
bytes: Bytes::copy_from_slice(bytes),
}
.to_range()
.expect("prefix range should be valid");
assert_eq!(actual, KeyRange { lower, upper });
}
}
}