use wdev::Device;
use wrecord::{HEADER_SIZE, MAX_FILLER_BYTES, RecordHeader, RecordMut, encode_to_slice};
use super::HybridLog;
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(),
});
}
let Some(slot_buf) = guard.get_mut(offset..offset + slot_size) else {
return Err(Error::RecordTooLarge {
size: slot_size,
page_size: self.config.page_size,
});
};
encode_to_slice(&mut slot_buf[..rec_size], prev_addr, key, val, is_tombstone)?;
let remaining = slot_size - rec_size;
if remaining >= HEADER_SIZE {
let pad_header = RecordHeader::pad(remaining);
slot_buf[rec_size..rec_size + HEADER_SIZE].copy_from_slice(&pad_header.to_bytes());
} else if remaining > 0 {
let mut hdr = RecordHeader::from_slice(&slot_buf[..HEADER_SIZE])?;
hdr.set_filler_bytes(remaining);
slot_buf[..HEADER_SIZE].copy_from_slice(&hdr.to_bytes());
slot_buf[rec_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)
}
}