pub mod ende;
pub(crate) mod macros;
pub(crate) mod staged;
pub use vsdb_core::common::*;
use vsdb_core::MapxRaw;
use error::Result;
use serde::{Serialize, de::DeserializeOwned};
use std::{any::type_name, fmt, fs, result::Result as StdResult, thread};
const TYPED_HANDLE_META_MAGIC: &[u8; 8] = b"VSTYPE03";
const LEGACY_TYPED_HANDLE_META_MAGIC: &[u8; 8] = b"VSTYPE02";
const TYPED_HANDLE_TAG_LEN: usize = 8;
#[derive(Clone, Copy, Debug)]
pub(crate) struct UnwindMark(bool);
impl UnwindMark {
#[inline(always)]
pub(crate) fn new() -> Self {
Self(thread::panicking())
}
#[inline(always)]
pub(crate) fn interrupted(self) -> bool {
!self.0 && thread::panicking()
}
}
pub(crate) trait Colocate: Sized {
fn new_colocated(anchor: &MapxRaw) -> Self;
fn raw(&self) -> &MapxRaw;
fn clone_colocated(&self, anchor: &MapxRaw) -> Result<Self>;
}
impl Colocate for MapxRaw {
#[inline(always)]
fn new_colocated(anchor: &MapxRaw) -> Self {
anchor.new_colocated()
}
#[inline(always)]
fn raw(&self) -> &MapxRaw {
self
}
fn clone_colocated(&self, anchor: &MapxRaw) -> Result<Self> {
MapxRaw::clone_colocated(self, anchor)
}
}
pub(crate) fn ensure_writable(ns: &Namespace, operation: &'static str) -> Result<()> {
if ns.is_read_only() {
Err(error::VsdbError::ReadOnly { operation })
} else {
Ok(())
}
}
pub(crate) fn ensure_process_writable(operation: &'static str) -> Result<()> {
if vsdb_open_mode() == OpenMode::ReadOnly {
Err(error::VsdbError::ReadOnly { operation })
} else {
Ok(())
}
}
fn fnv1a64(bytes: &[u8]) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0100_0000_01b3;
bytes.iter().fold(FNV_OFFSET, |h, &b| {
(h ^ u64::from(b)).wrapping_mul(FNV_PRIME)
})
}
fn map_type_paths(full: &str, mut map: impl FnMut(&str) -> &str) -> String {
let mut out = String::with_capacity(full.len());
let mut start = 0;
for (i, c) in full.char_indices() {
if !(c.is_alphanumeric() || c == '_' || c == ':') {
out.push_str(map(&full[start..i]));
out.push(c);
start = i + c.len_utf8();
}
}
out.push_str(map(&full[start..]));
out
}
fn type_tag<T: ?Sized>() -> u64 {
fnv1a64(type_name::<T>().as_bytes())
}
fn legacy_type_name(full: &str) -> String {
use crate::versioned::{BranchId, CommitId};
map_type_paths(full, |path| {
if path == type_name::<BranchId>() || path == type_name::<CommitId>() {
"u64"
} else {
path
}
})
}
fn legacy_type_tag<T: ?Sized>() -> u64 {
fnv1a64(legacy_type_name(type_name::<T>()).as_bytes())
}
pub fn save_instance_meta(id: InstanceId, value: &impl Serialize) -> Result<()> {
let ns = match id.ns {
None => Namespace::default_ns(),
Some(n) => Namespace::open(n)?,
};
ensure_writable(&ns, "metadata save")?;
let path = ns.meta_path(id.map_id);
create_dir_all_durable(path.parent().expect("has parent"))?;
let bytes = postcard::to_allocvec(value)?;
atomic_write_file(&path, &bytes)?;
Ok(())
}
pub fn load_instance_meta<T: DeserializeOwned>(id: InstanceId) -> Result<T> {
let ns = match id.ns {
None => Namespace::default_ns(),
Some(n) => Namespace::open(n)?,
};
let path = ns.meta_path(id.map_id);
let bytes = fs::read(&path)?;
Ok(postcard::from_bytes(&bytes)?)
}
pub(crate) fn load_instance_meta_checked<T>(
id: InstanceId,
instance_id: impl FnOnce(&T) -> InstanceId,
) -> Result<T>
where
T: DeserializeOwned,
{
let id = InstanceId::new(id.map_id, id.ns.unwrap_or(DEFAULT_NS_ID));
let value = load_instance_meta(id)?;
let found = instance_id(&value);
if found != id {
return Err(error::VsdbError::Decode {
detail: format!(
"metadata identity mismatch: requested {id}, payload names {found}"
),
});
}
Ok(value)
}
pub(crate) fn serialize_typed_handle_meta<T, Ser>(
inner: &impl Serialize,
serializer: Ser,
) -> StdResult<Ser::Ok, Ser::Error>
where
T: ?Sized,
Ser: serde::Serializer,
{
let bytes =
encode_typed_handle_meta::<T>(inner).map_err(serde::ser::Error::custom)?;
serializer.serialize_bytes(&bytes)
}
pub(crate) fn deserialize_typed_handle_meta<'de, T, Inner, De>(
deserializer: De,
) -> StdResult<Inner, De::Error>
where
T: ?Sized,
Inner: DeserializeOwned,
De: serde::Deserializer<'de>,
{
struct BytesVisitor;
impl<'de> serde::de::Visitor<'de> for BytesVisitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("typed VSDB handle metadata")
}
fn visit_bytes<E>(self, v: &[u8]) -> StdResult<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.to_vec())
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> StdResult<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v)
}
fn visit_seq<A>(self, mut seq: A) -> StdResult<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut ret = vec![];
while let Some(i) = seq.next_element()? {
ret.push(i);
}
Ok(ret)
}
}
deserializer
.deserialize_byte_buf(BytesVisitor)
.and_then(|meta| {
decode_typed_handle_meta::<T, Inner>(&meta).map_err(serde::de::Error::custom)
})
}
fn encode_typed_handle_meta<T: ?Sized>(inner: &impl Serialize) -> Result<Vec<u8>> {
let inner = postcard::to_allocvec(inner)?;
let mut out = Vec::with_capacity(
TYPED_HANDLE_META_MAGIC.len() + TYPED_HANDLE_TAG_LEN + inner.len(),
);
out.extend_from_slice(TYPED_HANDLE_META_MAGIC);
out.extend_from_slice(&type_tag::<T>().to_le_bytes());
out.extend_from_slice(&inner);
Ok(out)
}
fn decode_typed_handle_meta<T, Inner>(meta: &[u8]) -> Result<Inner>
where
T: ?Sized,
Inner: DeserializeOwned,
{
let magic_len = TYPED_HANDLE_META_MAGIC.len();
let header_len = magic_len + TYPED_HANDLE_TAG_LEN;
if meta.len() < header_len {
return Err(error::VsdbError::Decode {
detail: "invalid typed handle metadata magic".to_owned(),
});
}
let expected = match &meta[..magic_len] {
m if m == TYPED_HANDLE_META_MAGIC => type_tag::<T>(),
m if m == LEGACY_TYPED_HANDLE_META_MAGIC => legacy_type_tag::<T>(),
_ => {
return Err(error::VsdbError::Decode {
detail: "invalid typed handle metadata magic".to_owned(),
});
}
};
let found = u64::from_le_bytes(meta[magic_len..header_len].try_into().unwrap());
if found != expected {
return Err(error::VsdbError::Decode {
detail: format!(
"typed handle mismatch: expected {} (tag {expected:016x}), found tag {found:016x}",
type_name::<T>()
),
});
}
Ok(postcard::from_bytes(&meta[header_len..])?)
}
#[cfg(test)]
mod type_tag_test {
use super::*;
mod a {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Row(pub u32);
}
mod b {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Row(pub u32);
}
#[test]
fn tags_distinguish_qualified_types_and_parameters() {
use crate::{Mapx, MapxOrd};
assert_ne!(
type_tag::<Mapx<u32, a::Row>>(),
type_tag::<Mapx<u32, b::Row>>()
);
assert_ne!(
type_tag::<Mapx<u32, a::Row>>(),
type_tag::<Mapx<u64, a::Row>>()
);
assert_ne!(
type_tag::<Mapx<u32, u32>>(),
type_tag::<MapxOrd<u32, u32>>()
);
assert_ne!(
legacy_type_tag::<Mapx<u32, a::Row>>(),
legacy_type_tag::<Mapx<u32, b::Row>>()
);
}
#[test]
fn legacy_vstype02_envelope_still_restores() {
use crate::Mapx;
let mut m: Mapx<u32, String> = Mapx::new();
m.insert(&1, &"one".to_string());
let current = postcard::to_allocvec(&m).unwrap();
let meta: Vec<u8> = postcard::from_bytes(¤t).unwrap();
assert_eq!(&meta[..8], TYPED_HANDLE_META_MAGIC);
let mut legacy = LEGACY_TYPED_HANDLE_META_MAGIC.to_vec();
legacy.extend_from_slice(&legacy_type_tag::<Mapx<u32, String>>().to_le_bytes());
legacy.extend_from_slice(&meta[16..]);
let legacy = postcard::to_allocvec(&legacy).unwrap();
let restored: Mapx<u32, String> = postcard::from_bytes(&legacy).unwrap();
assert_eq!(restored.get(&1), Some("one".to_string()));
assert!(postcard::from_bytes::<Mapx<u64, String>>(&legacy).is_err());
let upgraded: Vec<u8> =
postcard::from_bytes(&postcard::to_allocvec(&restored).unwrap()).unwrap();
assert_eq!(&upgraded[..8], TYPED_HANDLE_META_MAGIC);
}
fn v16_meta<T>(inner: &impl Serialize) -> Vec<u8> {
let mut meta = b"VSTYPE02".to_vec();
meta.extend_from_slice(&fnv1a64(type_name::<T>().as_bytes()).to_le_bytes());
meta.extend_from_slice(&postcard::to_allocvec(inner).unwrap());
meta
}
#[test]
fn legacy_handles_restore_versioning_id_aliases() {
use crate::{
Mapx, MapxOrdRawKey, Orphan,
versioned::{BranchId, CommitId},
};
let mut raw = MapxRaw::new();
raw.insert(postcard::to_allocvec("tip").unwrap(), [7]);
let inner = v16_meta::<MapxOrdRawKey<u64>>(&raw);
let outer = v16_meta::<Mapx<String, u64>>(&inner);
let bytes = postcard::to_allocvec(&outer).unwrap();
let restored: Mapx<String, CommitId> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(restored.get("tip"), Some(CommitId::from_raw(7)));
let mut raw = MapxRaw::new();
raw.insert([3, 5], postcard::to_allocvec(&vec![7u64, 9]).unwrap());
let inner = v16_meta::<MapxOrdRawKey<Vec<u64>>>(&raw);
let outer = v16_meta::<Mapx<(u64, u64), Vec<u64>>>(&inner);
let bytes = postcard::to_allocvec(&outer).unwrap();
let restored: Mapx<(BranchId, CommitId), Vec<CommitId>> =
postcard::from_bytes(&bytes).unwrap();
assert_eq!(
restored.get(&(BranchId::from_raw(3), CommitId::from_raw(5))),
Some(vec![CommitId::from_raw(7), CommitId::from_raw(9)])
);
let mut raw = MapxRaw::new();
raw.insert([], [11]);
let inner = v16_meta::<MapxOrdRawKey<u64>>(&raw);
let outer = v16_meta::<Orphan<u64>>(&inner);
let bytes = postcard::to_allocvec(&outer).unwrap();
let restored: Orphan<BranchId> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(restored.get_value(), BranchId::from_raw(11));
let upgraded = postcard::to_allocvec(&restored).unwrap();
let reopened: Orphan<BranchId> = postcard::from_bytes(&upgraded).unwrap();
assert_eq!(reopened.get_value(), BranchId::from_raw(11));
assert!(postcard::from_bytes::<Orphan<CommitId>>(&upgraded).is_err());
assert!(postcard::from_bytes::<Orphan<u64>>(&upgraded).is_err());
assert!(postcard::from_bytes::<Orphan<u32>>(&bytes).is_err());
}
#[test]
fn legacy_ordered_handles_restore_versioning_id_aliases() {
use crate::{
MapxOrd, MapxOrdRawKey,
common::ende::KeyEnDeOrdered,
versioned::{BranchId, CommitId},
};
fn check<Id: KeyEnDeOrdered, Other>(id: fn(u64) -> Id) {
let mut raw = MapxRaw::new();
for (key, value) in [(256u64, 3u8), (1, 1), (255, 2)] {
raw.insert(key.to_be_bytes(), [value]);
}
let inner = v16_meta::<MapxOrdRawKey<u8>>(&raw);
let outer = v16_meta::<MapxOrd<u64, u8>>(&inner);
let bytes = postcard::to_allocvec(&outer).unwrap();
let mut restored: MapxOrd<Id, u8> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(restored.get(&id(256)), Some(3));
assert_eq!(
restored.iter().collect::<Vec<_>>(),
vec![(id(1), 1), (id(255), 2), (id(256), 3)]
);
assert_eq!(
restored.range(id(255)..=id(256)).collect::<Vec<_>>(),
vec![(id(255), 2), (id(256), 3)]
);
restored.insert(&id(257), &4);
restored.insert(&id(255), &5);
assert_eq!(raw.get(257u64.to_be_bytes()), Some(vec![4]));
assert_eq!(raw.get(255u64.to_be_bytes()), Some(vec![5]));
let upgraded = postcard::to_allocvec(&restored).unwrap();
let meta: Vec<u8> = postcard::from_bytes(&upgraded).unwrap();
assert_eq!(&meta[..8], TYPED_HANDLE_META_MAGIC);
let reopened: MapxOrd<Id, u8> = postcard::from_bytes(&upgraded).unwrap();
assert_eq!(
reopened.iter().collect::<Vec<_>>(),
vec![(id(1), 1), (id(255), 5), (id(256), 3), (id(257), 4)]
);
assert!(postcard::from_bytes::<MapxOrd<Other, u8>>(&upgraded).is_err());
assert!(postcard::from_bytes::<MapxOrd<u64, u8>>(&upgraded).is_err());
}
check::<BranchId, CommitId>(BranchId::from_raw);
check::<CommitId, BranchId>(CommitId::from_raw);
}
#[test]
fn legacy_id_aliases_match_only_complete_library_type_paths() {
use crate::versioned::CommitId;
let id = type_name::<CommitId>();
assert_eq!(
legacy_type_name(&format!("({id}, [{id}; 2])")),
"(u64, [u64; 2])"
);
for unrelated in [
format!("{id}Extra"),
format!("app::{id}"),
"app::CommitId".into(),
] {
assert_eq!(legacy_type_name(&unrelated), unrelated);
}
}
}