use wdev::Device;
use wrecord::{HEADER_SIZE, MAX_FILLER_BYTES, RecordHeader, RecordMut, encode_to_slice};
use super::{HybridLog, PAD_KEY_LEN};
use crate::error::{Error, Result};
impl<D: Device> HybridLog<D> {
pub fn try_update_in_place(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if rec_mut.is_tombstone() || !rec_mut.can_update_with_slack(new_val.len()) {
return Ok(false);
}
rec_mut.update_value_with_slack(new_val)?;
Ok(true)
})
}
pub fn try_mark_tombstone_in_place(&self, addr: u64, expected_key: &[u8]) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if rec_mut.is_tombstone() {
return Ok(false);
}
rec_mut.set_tombstone(true);
Ok(true)
})
}
pub fn try_modify_record_in_place<R>(
&self,
addr: u64,
expected_key: &[u8],
f: impl FnOnce(&mut [u8]) -> Option<R>,
) -> Result<Option<R>> {
self.with_mutable_record(addr, expected_key, None, |rec_mut| {
if rec_mut.is_tombstone() {
return Ok(None);
}
Ok(f(rec_mut.value_mut()))
})
}
#[inline]
pub fn try_modify_record_with_slack(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.try_update_in_place(addr, expected_key, new_val)
}
pub fn try_revivify_in_chain(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if !rec_mut.is_tombstone()
|| new_val.len() > rec_mut.val_capacity()
|| (rec_mut.val_capacity() - new_val.len()) > MAX_FILLER_BYTES
{
return Ok(false);
}
rec_mut.revivify_with_slack(new_val)?;
Ok(true)
})
}
pub fn revivify_record_at(
&self,
addr: u64,
slot_size: usize,
key: &[u8],
val: &[u8],
prev_addr: u64,
is_tombstone: bool,
) -> Result<()> {
if !self.addresses.is_mutable(addr) {
return Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
});
}
let p = super::RecParams {
prev_addr,
key,
val,
is_tombstone,
};
let rec_size = self.validate_append_args(&p)?;
if slot_size < rec_size {
return Err(Error::RecordTooLarge {
size: rec_size,
page_size: slot_size,
});
}
let page_id = self.config.page_id(addr);
let offset = self.config.page_offset(addr);
if !self.buffer.is_page_loaded(page_id) {
return Err(Error::PageNotReady(page_id));
}
let mut guard = self.buffer.write_page(page_id);
if !self.addresses.is_mutable(addr) || !self.buffer.is_page_loaded(page_id) {
return Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
});
}
if offset + slot_size > self.config.page_size {
return Err(Error::RecordTooLarge {
size: slot_size,
page_size: self.config.page_size,
});
}
encode_to_slice(
&mut guard[offset..offset + rec_size],
prev_addr,
key,
val,
is_tombstone,
)?;
let remaining = slot_size - rec_size;
if remaining >= HEADER_SIZE {
let pad_header = RecordHeader {
prev_address: 0,
key_len: PAD_KEY_LEN,
val_len: (remaining - HEADER_SIZE) as u32,
};
guard[offset + rec_size..offset + rec_size + HEADER_SIZE]
.copy_from_slice(&pad_header.to_bytes());
} else if remaining > 0 {
let mut hdr = RecordHeader::from_slice(&guard[offset..offset + HEADER_SIZE])?;
hdr.set_filler_bytes(remaining);
guard[offset..offset + HEADER_SIZE].copy_from_slice(&hdr.to_bytes());
guard[offset + rec_size..offset + slot_size].fill(0);
}
Ok(())
}
fn with_mutable_record<T>(
&self,
addr: u64,
expected_key: &[u8],
degraded: T,
f: impl FnOnce(&mut RecordMut<'_>) -> Result<T>,
) -> Result<T> {
if !self.addresses.is_mutable(addr) {
return Ok(degraded);
}
let page_id = self.config.page_id(addr);
let offset = self.config.page_offset(addr);
if offset + HEADER_SIZE > self.config.page_size || !self.buffer.is_page_loaded(page_id) {
return Ok(degraded);
}
let mut guard = self.buffer.write_page(page_id);
if !self.addresses.is_mutable(addr) || !self.buffer.is_page_loaded(page_id) {
return Ok(degraded);
}
let mut rec_mut = match RecordMut::from_slice_mut(&mut guard[offset..]) {
Ok(r) => r,
Err(_) => return Ok(degraded),
};
if !rec_mut.matches_key(expected_key) {
return Ok(degraded);
}
f(&mut rec_mut)
}
}