mod codec;
#[cfg(test)]
mod tests;
use tessarikv_constants::{MAX_BATCH_BYTES, MAX_KEY_BYTES, MAX_VALUE_BYTES};
use tessarikv_types::{KeyspaceId, ValueKind};
use crate::{Error, Result};
const FORMAT_VERSION: u8 = 2;
const FORMAT_VERSION_1: u8 = 1;
const HEADER_BYTES: usize = 5;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteBatch {
rep: Vec<u8>,
count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BatchEntry<'a> {
pub keyspace: KeyspaceId,
pub kind: ValueKind,
pub key: &'a [u8],
pub value: Option<&'a [u8]>,
}
impl Default for WriteBatch {
fn default() -> Self {
Self::new()
}
}
impl WriteBatch {
#[must_use]
pub fn new() -> Self {
let mut rep = Vec::with_capacity(HEADER_BYTES);
rep.push(FORMAT_VERSION);
rep.extend_from_slice(&0_u32.to_le_bytes());
Self { rep, count: 0 }
}
pub fn put(&mut self, keyspace: KeyspaceId, key: &[u8], value: &[u8]) -> Result<()> {
self.append(keyspace, ValueKind::Value, key, Some(value))
}
pub fn delete(&mut self, keyspace: KeyspaceId, key: &[u8]) -> Result<()> {
self.append(keyspace, ValueKind::Delete, key, None)
}
pub fn delete_range(&mut self, keyspace: KeyspaceId, start: &[u8], end: &[u8]) -> Result<()> {
if start >= end {
return Err(empty_range());
}
check_size("range end", end.len(), MAX_KEY_BYTES)?;
self.append(keyspace, ValueKind::RangeDelete, start, Some(end))
}
#[must_use]
pub fn len(&self) -> u32 {
self.count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub fn size_bytes(&self) -> usize {
self.rep.len()
}
pub fn clear(&mut self) {
self.rep.truncate(HEADER_BYTES);
self.set_count(0);
}
#[must_use]
pub fn iter(&self) -> BatchIter<'_> {
BatchIter {
rep: &self.rep,
at: HEADER_BYTES,
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.rep
}
pub fn from_bytes(rep: Vec<u8>) -> Result<Self> {
check_size("write batch", rep.len(), MAX_BATCH_BYTES)?;
let version = codec::read_u8(&rep, 0, "write batch")?;
if version != FORMAT_VERSION && version != FORMAT_VERSION_1 {
return Err(Error::UnsupportedVersion {
what: "write batch",
version,
});
}
let count = codec::read_u32(&rep, 1, "write batch")?;
let mut at = HEADER_BYTES;
for _ in 0..count {
(_, at) = codec::read_entry(&rep, at, version)?;
}
if at != rep.len() {
return Err(Error::Malformed {
what: "write batch",
reason: "bytes follow the last entry",
});
}
Ok(Self { rep, count })
}
fn append(
&mut self,
keyspace: KeyspaceId,
kind: ValueKind,
key: &[u8],
value: Option<&[u8]>,
) -> Result<()> {
check_size("key", key.len(), MAX_KEY_BYTES)?;
if let Some(value) = value {
check_size("value", value.len(), MAX_VALUE_BYTES)?;
}
let count = self.count.checked_add(1).ok_or(Error::TooLarge {
what: "write batch entry count",
size: u64::from(u32::MAX).saturating_add(1),
limit: u64::from(u32::MAX),
})?;
let entry_bytes = codec::entry_bytes(key, value).ok_or(too_large_batch(usize::MAX))?;
let total = self
.rep
.len()
.checked_add(entry_bytes)
.ok_or(too_large_batch(usize::MAX))?;
check_size("write batch", total, MAX_BATCH_BYTES)?;
codec::write_entry(&mut self.rep, keyspace, kind, key, value)?;
self.set_count(count);
Ok(())
}
fn set_count(&mut self, count: u32) {
self.count = count;
if let Some(field) = self.rep.get_mut(1..HEADER_BYTES) {
field.copy_from_slice(&count.to_le_bytes());
}
}
}
#[derive(Debug, Clone)]
pub struct BatchIter<'a> {
rep: &'a [u8],
at: usize,
}
impl<'a> Iterator for BatchIter<'a> {
type Item = BatchEntry<'a>;
fn next(&mut self) -> Option<BatchEntry<'a>> {
if self.at >= self.rep.len() {
return None;
}
let version = self.rep.first().copied().unwrap_or(FORMAT_VERSION);
let (entry, next) = codec::read_entry(self.rep, self.at, version).ok()?;
self.at = next;
Some(entry)
}
}
impl<'a> IntoIterator for &'a WriteBatch {
type Item = BatchEntry<'a>;
type IntoIter = BatchIter<'a>;
fn into_iter(self) -> BatchIter<'a> {
self.iter()
}
}
fn check_size(what: &'static str, size: usize, limit: usize) -> Result<()> {
if size > limit {
return Err(Error::TooLarge {
what,
size: u64::try_from(size).unwrap_or(u64::MAX),
limit: u64::try_from(limit).unwrap_or(u64::MAX),
});
}
Ok(())
}
fn too_large_batch(size: usize) -> Error {
Error::TooLarge {
what: "write batch",
size: u64::try_from(size).unwrap_or(u64::MAX),
limit: u64::try_from(MAX_BATCH_BYTES).unwrap_or(u64::MAX),
}
}
pub(super) fn empty_range() -> Error {
Error::Malformed {
what: "range delete",
reason: "the start is not below the end",
}
}