armdb 0.12.0

sharded bitcask key-value storage optimized for NVMe
Documentation
use crate::codec::Codec;
use crate::error::{DbError, DbResult};

use super::Wire;

#[derive(Clone, Copy, Default)]
pub(crate) struct CodecWire<C>(pub C);

impl<V, C: Codec<V> + Send + Sync + 'static> Wire<V> for CodecWire<C> {
    /// Storage codec bytes are wire-identical. Typed/VarTyped stay off the raw
    /// shortcut via `Access::RAW_WIRE_BYTES = false`; only LSM opts in on both sides.
    const PASSTHROUGH: bool = true;

    fn encode_to(&self, value: &V, out: &mut Vec<u8>) -> DbResult<()> {
        self.0.encode_to(value, out)
    }
    fn decode(&self, bytes: &[u8]) -> DbResult<V> {
        self.0.decode_from(bytes)
    }
    fn size(&self, value: &V) -> DbResult<u32> {
        Ok(self.0.size(value) as u32)
    }
    fn validate_stored(&self, raw: &[u8]) -> DbResult<()> {
        self.0.decode_from(raw).map(|_| ())
    }
}

/// Zerocopy fixed-width wire. `V` is the byte width (`const V: usize` on Zero collections).
pub(crate) struct ZeroWire<const V: usize>;

impl<T, const V: usize> Wire<T> for ZeroWire<V>
where
    T: Copy
        + zerocopy::IntoBytes
        + zerocopy::FromBytes
        + zerocopy::Immutable
        + Send
        + Sync
        + 'static,
{
    fn encode_to(&self, value: &T, out: &mut Vec<u8>) -> DbResult<()> {
        out.clear();
        out.extend_from_slice(crate::zero_tree::to_bytes::<V, T>(value).as_slice());
        Ok(())
    }
    fn decode(&self, bytes: &[u8]) -> DbResult<T> {
        if bytes.len() != V {
            return Err(DbError::Client("invalid value length for zero collection"));
        }
        let arr: &[u8; V] = bytes
            .try_into()
            .map_err(|_| DbError::Client("invalid value length for zero collection"))?;
        Ok(crate::zero_tree::from_value_bytes(arr))
    }
    fn size(&self, _value: &T) -> DbResult<u32> {
        Ok(V as u32)
    }
    fn decode_batch_item(&self, bytes: &[u8]) -> DbResult<T> {
        if bytes.len() != V {
            return Err(DbError::Client("invalid value length in batch"));
        }
        self.decode(bytes)
            .map_err(|_| DbError::Client("invalid value length in batch"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::DbError;

    #[cfg(feature = "rapira-codec")]
    mod rapira_codec {
        use super::*;
        use crate::codec::RapiraCodec;

        #[test]
        fn codec_wire_round_trips_and_is_passthrough() {
            let w = CodecWire(RapiraCodec);
            assert!(<CodecWire<RapiraCodec> as Wire<u64>>::PASSTHROUGH);
            let mut out = Vec::new();
            Wire::<u64>::encode_to(&w, &7u64, &mut out).unwrap();
            assert_eq!(Wire::<u64>::decode(&w, &out).unwrap(), 7u64);
            assert_eq!(Wire::<u64>::size(&w, &7u64).unwrap(), out.len() as u32);
            // batch default message
            let err = Wire::<u64>::decode_batch_item(&w, &[0xff, 0xff]).unwrap_err();
            assert!(matches!(
                err,
                DbError::Client("invalid value bytes in batch")
            ));
        }

        #[test]
        fn validate_stored_default_errors_and_codec_checks_decode() {
            let w = CodecWire(RapiraCodec);
            // valid rapira u64
            let bytes = Wire::<u64>::encode(&w, &1u64).unwrap();
            Wire::<u64>::validate_stored(&w, &bytes).unwrap();
            assert!(Wire::<u64>::validate_stored(&w, &[0xff; 3]).is_err());
        }
    }

    #[test]
    fn zero_wire_rejects_bad_length_with_upsert_message() {
        let w = ZeroWire::<8>;
        assert!(!<ZeroWire<8> as Wire<u64>>::PASSTHROUGH);
        let err = Wire::<u64>::decode(&w, &[1, 2, 3]).unwrap_err();
        assert!(matches!(
            err,
            DbError::Client("invalid value length for zero collection")
        ));
        let err = Wire::<u64>::decode_batch_item(&w, &[1, 2, 3]).unwrap_err();
        assert!(matches!(
            err,
            DbError::Client("invalid value length in batch")
        ));
    }
}