use crate::Key;
use crate::error::{DbError, DbResult};
#[cfg(feature = "typed-tree")]
use crate::projection::ProjectionSink;
use std::mem::size_of;
pub trait WriteHook<K: Key>: Send + Sync {
const NEEDS_OLD_VALUE: bool = true;
const NEEDS_INIT: bool = false;
const NEEDS_WRITE: bool = true;
const EXPIRES: bool = false;
const TTL_REVISION: u32 = 0;
fn deadline(&self, _key: &K, _value: &[u8]) -> Option<u64> {
None
}
fn ttl_decode_failed(&self, _value: &[u8]) -> bool {
false
}
fn on_write(&self, key: &K, old: Option<&[u8]>, new: Option<&[u8]>);
fn on_init(&self, _key: &K, _value: &[u8]) {}
#[cfg(feature = "typed-tree")]
const PROJECTS: bool = false;
#[cfg(feature = "typed-tree")]
fn project(
&self,
_key: &K,
_old: Option<&[u8]>,
_new: Option<&[u8]>,
_sink: &mut ProjectionSink<'_>,
) -> DbResult<()> {
Ok(())
}
#[cfg(feature = "typed-tree")]
fn projection_targets(&self) -> Option<&crate::projection::IndexTargets> {
None
}
#[cfg(feature = "typed-tree")]
fn projection_limits(&self) -> (usize, usize) {
(
crate::projection::DEFAULT_MAX_PROJECTED_ROWS,
crate::projection::DEFAULT_MAX_PROJECTED_BYTES,
)
}
}
pub struct NoHook;
impl<K: Key> WriteHook<K> for NoHook {
const NEEDS_OLD_VALUE: bool = false;
const NEEDS_INIT: bool = false;
const NEEDS_WRITE: bool = false;
#[inline(always)]
fn on_write(&self, _key: &K, _old: Option<&[u8]>, _new: Option<&[u8]>) {}
#[inline(always)]
fn on_init(&self, _key: &K, _value: &[u8]) {}
#[cfg(feature = "typed-tree")]
const PROJECTS: bool = false;
#[cfg(feature = "typed-tree")]
fn project(
&self,
_key: &K,
_old: Option<&[u8]>,
_new: Option<&[u8]>,
_sink: &mut ProjectionSink<'_>,
) -> DbResult<()> {
Ok(())
}
}
#[cfg(feature = "typed-tree")]
pub trait TypedWriteHook<K: Key, T>: Send + Sync {
const NEEDS_OLD_VALUE: bool = true;
const NEEDS_INIT: bool = false;
const NEEDS_WRITE: bool = true;
const EXPIRES: bool = false;
const TTL_REVISION: u32 = 0;
fn deadline(&self, _key: &K, _value: &T) -> Option<u64> {
None
}
fn on_write(&self, key: &K, old: Option<&T>, new: Option<&T>);
fn on_init(&self, _key: &K, _value: &T) {}
const PROJECTS: bool = false;
fn project(&self, _key: &K, _value: &T, _sink: &mut ProjectionSink<'_>) -> DbResult<()> {
Ok(())
}
fn projection_targets(&self) -> Option<&crate::projection::IndexTargets> {
None
}
fn projection_limits(&self) -> (usize, usize) {
(
crate::projection::DEFAULT_MAX_PROJECTED_ROWS,
crate::projection::DEFAULT_MAX_PROJECTED_BYTES,
)
}
}
#[cfg(feature = "typed-tree")]
impl<K: Key, T> TypedWriteHook<K, T> for NoHook {
const NEEDS_OLD_VALUE: bool = false;
const NEEDS_INIT: bool = false;
const NEEDS_WRITE: bool = false;
#[inline(always)]
fn on_write(&self, _key: &K, _old: Option<&T>, _new: Option<&T>) {}
#[inline(always)]
fn on_init(&self, _key: &K, _value: &T) {}
const PROJECTS: bool = false;
fn project(&self, _key: &K, _value: &T, _sink: &mut ProjectionSink<'_>) -> DbResult<()> {
Ok(())
}
}
#[cfg(feature = "typed-tree")]
pub struct ZeroHookAdapter<K, T, H> {
pub(crate) inner: H,
pub(crate) _marker: std::marker::PhantomData<fn() -> (K, T)>,
}
#[cfg(feature = "typed-tree")]
impl<K, T, H> ZeroHookAdapter<K, T, H> {
pub fn new(inner: H) -> Self {
Self {
inner,
_marker: std::marker::PhantomData,
}
}
}
#[cfg(feature = "typed-tree")]
unsafe impl<K, T, H: Send> Send for ZeroHookAdapter<K, T, H> {}
#[cfg(feature = "typed-tree")]
unsafe impl<K, T, H: Sync> Sync for ZeroHookAdapter<K, T, H> {}
#[cfg(feature = "typed-tree")]
impl<K: Key, T: Copy + zerocopy::FromBytes, H: TypedWriteHook<K, T>> WriteHook<K>
for ZeroHookAdapter<K, T, H>
{
const NEEDS_OLD_VALUE: bool = H::NEEDS_OLD_VALUE;
const NEEDS_INIT: bool = H::NEEDS_INIT;
const NEEDS_WRITE: bool = H::NEEDS_WRITE;
const EXPIRES: bool = H::EXPIRES;
const TTL_REVISION: u32 = H::TTL_REVISION;
const PROJECTS: bool = H::PROJECTS;
fn deadline(&self, key: &K, value: &[u8]) -> Option<u64> {
debug_assert_eq!(value.len(), size_of::<T>());
let val: T = zerocopy::FromBytes::read_from_bytes(value).ok()?;
self.inner.deadline(key, &val)
}
#[inline]
fn on_write(&self, key: &K, old: Option<&[u8]>, new: Option<&[u8]>) {
if !H::NEEDS_WRITE {
return;
}
let read = |b: &[u8]| -> T {
debug_assert_eq!(b.len(), size_of::<T>());
zerocopy::FromBytes::read_from_bytes(b).expect("slice len == size_of::<T>()")
};
let old_val = old.map(read);
let new_val = new.map(read);
self.inner.on_write(key, old_val.as_ref(), new_val.as_ref());
}
#[inline]
fn on_init(&self, key: &K, value: &[u8]) {
debug_assert_eq!(value.len(), size_of::<T>());
let val: T =
zerocopy::FromBytes::read_from_bytes(value).expect("slice len == size_of::<T>()");
self.inner.on_init(key, &val);
}
fn project(
&self,
key: &K,
_old: Option<&[u8]>,
new: Option<&[u8]>,
sink: &mut ProjectionSink<'_>,
) -> DbResult<()> {
if !H::PROJECTS {
return Ok(());
}
if let Some(b) = new {
debug_assert_eq!(b.len(), size_of::<T>());
let val: T = zerocopy::FromBytes::read_from_bytes(b).map_err(|_| {
DbError::FormatMismatch("zero hook adapter project decode failed".into())
})?;
self.inner.project(key, &val, sink)?;
}
Ok(())
}
fn projection_targets(&self) -> Option<&crate::projection::IndexTargets> {
self.inner.projection_targets()
}
fn projection_limits(&self) -> (usize, usize) {
self.inner.projection_limits()
}
}
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
pub struct VarTypedHookAdapter<K, T, C, H> {
pub(crate) inner: H,
pub(crate) codec: C,
pub(crate) _marker: std::marker::PhantomData<fn() -> (K, T)>,
}
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
impl<K, T, C, H> VarTypedHookAdapter<K, T, C, H> {
pub fn new(inner: H, codec: C) -> Self {
Self {
inner,
codec,
_marker: std::marker::PhantomData,
}
}
}
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
impl<K, T, C, H> VarTypedHookAdapter<K, T, C, H>
where
K: Key,
T: Send + Sync,
H: TypedWriteHook<K, T>,
{
pub(crate) fn typed_deadline(&self, key: &K, value: &T) -> Option<u64> {
if H::EXPIRES {
self.inner.deadline(key, value)
} else {
None
}
}
}
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
unsafe impl<K, T, C: Send, H: Send> Send for VarTypedHookAdapter<K, T, C, H> {}
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
unsafe impl<K, T, C: Sync, H: Sync> Sync for VarTypedHookAdapter<K, T, C, H> {}
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
impl<K, T, C, H> WriteHook<K> for VarTypedHookAdapter<K, T, C, H>
where
K: Key,
T: Send + Sync,
C: crate::codec::Codec<T>,
H: TypedWriteHook<K, T>,
{
const NEEDS_OLD_VALUE: bool = H::NEEDS_OLD_VALUE;
const NEEDS_INIT: bool = H::NEEDS_INIT;
const NEEDS_WRITE: bool = H::NEEDS_WRITE;
const EXPIRES: bool = H::EXPIRES;
const TTL_REVISION: u32 = H::TTL_REVISION;
const PROJECTS: bool = H::PROJECTS;
fn deadline(&self, key: &K, value: &[u8]) -> Option<u64> {
self.codec
.decode_from(value)
.ok()
.and_then(|val| self.inner.deadline(key, &val))
}
fn ttl_decode_failed(&self, value: &[u8]) -> bool {
self.codec.decode_from(value).is_err()
}
#[inline]
fn on_write(&self, key: &K, old: Option<&[u8]>, new: Option<&[u8]>) {
if !H::NEEDS_WRITE {
return;
}
let old_t = old.and_then(|b| {
self.codec
.decode_from(b)
.map_err(|_| {
tracing::warn!(
side = "old",
value_len = b.len(),
"var_typed hook adapter: on_write decode failed"
);
})
.ok()
});
let new_t = new.and_then(|b| {
self.codec
.decode_from(b)
.map_err(|_| {
tracing::warn!(
side = "new",
value_len = b.len(),
"var_typed hook adapter: on_write decode failed"
);
})
.ok()
});
self.inner.on_write(key, old_t.as_ref(), new_t.as_ref());
}
#[inline]
fn on_init(&self, key: &K, value: &[u8]) {
match self.codec.decode_from(value) {
Ok(val) => self.inner.on_init(key, &val),
Err(_) => {
tracing::warn!(
key_len = std::mem::size_of::<K>(),
value_len = value.len(),
"var_typed hook adapter: on_init decode failed, entry skipped"
);
}
}
}
fn project(
&self,
key: &K,
_old: Option<&[u8]>,
new: Option<&[u8]>,
sink: &mut ProjectionSink<'_>,
) -> DbResult<()> {
if !H::PROJECTS {
return Ok(());
}
if let Some(b) = new {
let val = self.codec.decode_from(b)?;
self.inner.project(key, &val, sink)?;
}
Ok(())
}
fn projection_targets(&self) -> Option<&crate::projection::IndexTargets> {
self.inner.projection_targets()
}
fn projection_limits(&self) -> (usize, usize) {
self.inner.projection_limits()
}
}