use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use arc_swap::ArcSwap;
use parking_lot::{Mutex, MutexGuard};
use crate::error::Error;
#[derive(Debug, thiserror::Error)]
pub enum MemoryError {
#[error("in-memory storage: malformed {0} key")]
MalformedKey(&'static str),
#[error("in-memory storage: {0} is not supported without a persistent backend")]
Unsupported(&'static str),
}
pub type StorageError = MemoryError;
type TableData = BTreeMap<Vec<u8>, BTreeSet<Vec<u8>>>;
const TABLE_COUNT: usize = 12;
const T_NODES: usize = 0;
const T_EDGES: usize = 1;
const T_OUT_ADJ: usize = 2;
const T_IN_ADJ: usize = 3;
const T_LABEL_IDX: usize = 4;
const T_TYPE_IDX: usize = 5;
const T_NODE_PROP_IDX: usize = 6;
const T_EDGE_PROP_IDX: usize = 7;
const T_FTS_POSTINGS: usize = 8;
const T_FTS_DOCS: usize = 9;
const T_VECTORS: usize = 10;
const T_META: usize = 11;
pub trait Key {
type Out<'a>;
fn encode(&self) -> Cow<'_, [u8]>;
fn decode(bytes: &[u8]) -> Result<Self::Out<'_>, MemoryError>;
}
impl Key for u64 {
type Out<'a> = u64;
fn encode(&self) -> Cow<'_, [u8]> {
Cow::Owned(self.to_be_bytes().to_vec())
}
fn decode(bytes: &[u8]) -> Result<u64, MemoryError> {
let array: [u8; 8] = bytes
.try_into()
.map_err(|_| MemoryError::MalformedKey("u64"))?;
Ok(u64::from_be_bytes(array))
}
}
impl Key for [u8] {
type Out<'a> = &'a [u8];
fn encode(&self) -> Cow<'_, [u8]> {
Cow::Borrowed(self)
}
fn decode(bytes: &[u8]) -> Result<&[u8], MemoryError> {
Ok(bytes)
}
}
impl Key for str {
type Out<'a> = &'a str;
fn encode(&self) -> Cow<'_, [u8]> {
Cow::Borrowed(self.as_bytes())
}
fn decode(bytes: &[u8]) -> Result<&str, MemoryError> {
std::str::from_utf8(bytes).map_err(|_| MemoryError::MalformedKey("str"))
}
}
type TypeMarker<K, V> = PhantomData<(fn() -> K, fn() -> V)>;
pub trait Val {
type Out<'a>;
fn encode(&self) -> &[u8];
fn decode(bytes: &[u8]) -> Self::Out<'_>;
}
impl Val for [u8] {
type Out<'a> = &'a [u8];
fn encode(&self) -> &[u8] {
self
}
fn decode(bytes: &[u8]) -> &[u8] {
bytes
}
}
impl Val for () {
type Out<'a> = ();
fn encode(&self) -> &[u8] {
&[]
}
fn decode(_: &[u8]) {}
}
#[derive(Clone)]
pub struct Env {
published: Arc<ArcSwap<Tables>>,
writer: Arc<Mutex<()>>,
}
type Tables = Vec<Arc<TableData>>;
impl Env {
fn new() -> Self {
Self {
published: Arc::new(ArcSwap::from_pointee(
(0..TABLE_COUNT)
.map(|_| Arc::new(TableData::new()))
.collect(),
)),
writer: Arc::new(Mutex::new(())),
}
}
pub fn read_txn(&self) -> Result<RoTxn<'_>, Error> {
Ok(RoTxn {
tables: self.published.load_full(),
marker: PhantomData,
})
}
pub fn write_txn(&self) -> Result<RwTxn<'_>, Error> {
let guard = self.writer.lock();
let working = Arc::new((*self.published.load_full()).clone());
Ok(RwTxn {
inner: RoTxn {
tables: working,
marker: PhantomData,
},
published: Arc::clone(&self.published),
_guard: guard,
})
}
}
pub struct RoTxn<'e> {
tables: Arc<Tables>,
marker: PhantomData<&'e ()>,
}
impl RoTxn<'_> {
fn table_data(&self, index: usize) -> &TableData {
&self.tables[index]
}
}
pub type OwnedRoTxn<'e> = RoTxn<'e>;
pub struct RwTxn<'e> {
inner: RoTxn<'e>,
published: Arc<ArcSwap<Tables>>,
_guard: MutexGuard<'e, ()>,
}
impl<'e> std::ops::Deref for RwTxn<'e> {
type Target = RoTxn<'e>;
fn deref(&self) -> &RoTxn<'e> {
&self.inner
}
}
impl RwTxn<'_> {
pub fn commit(self) -> Result<(), Error> {
self.published.store(Arc::clone(&self.inner.tables));
Ok(())
}
pub fn abort(self) {}
fn table_mut(&mut self, index: usize) -> &mut TableData {
let tables = Arc::make_mut(&mut self.inner.tables);
Arc::make_mut(&mut tables[index])
}
}
pub struct Table<K: ?Sized, V: ?Sized> {
index: usize,
duplicates: bool,
marker: TypeMarker<K, V>,
}
impl<K: ?Sized, V: ?Sized> Clone for Table<K, V> {
fn clone(&self) -> Self {
*self
}
}
impl<K: ?Sized, V: ?Sized> Copy for Table<K, V> {}
impl<K: ?Sized, V: ?Sized> Table<K, V> {
const fn new(index: usize, duplicates: bool) -> Self {
Self {
index,
duplicates,
marker: PhantomData,
}
}
}
impl<K: Key + ?Sized, V: Val + ?Sized> Table<K, V> {
pub fn get<'t>(&self, txn: &'t RoTxn<'_>, key: &K) -> Result<Option<V::Out<'t>>, Error> {
let encoded = key.encode();
Ok(txn
.table_data(self.index)
.get(encoded.as_ref())
.and_then(|values| values.first())
.map(|value| V::decode(value)))
}
pub fn len(&self, txn: &RoTxn<'_>) -> Result<u64, Error> {
Ok(txn
.table_data(self.index)
.values()
.map(|values| values.len() as u64)
.sum())
}
pub fn iter<'t>(&self, txn: &'t RoTxn<'_>) -> Result<TableIter<'t, K, V>, Error> {
Ok(TableIter::new(
txn.table_data(self.index)
.iter()
.flat_map(|(k, values)| values.iter().map(move |v| (k.as_slice(), v.as_slice())))
.collect(),
))
}
pub fn prefix_iter<'t>(
&self,
txn: &'t RoTxn<'_>,
prefix: &K,
) -> Result<TableIter<'t, K, V>, Error> {
let encoded = prefix.encode().into_owned();
Ok(TableIter::new(
txn.table_data(self.index)
.range(encoded.clone()..)
.take_while(move |(k, _)| k.starts_with(&encoded))
.flat_map(|(k, values)| values.iter().map(move |v| (k.as_slice(), v.as_slice())))
.collect(),
))
}
pub fn get_duplicates<'t>(
&self,
txn: &'t RoTxn<'_>,
key: &K,
) -> Result<Option<TableIter<'t, K, V>>, Error> {
let encoded = key.encode();
let Some((stored_key, values)) = txn.table_data(self.index).get_key_value(encoded.as_ref())
else {
return Ok(None);
};
if values.is_empty() {
return Ok(None);
}
Ok(Some(TableIter::new(
values
.iter()
.map(|v| (stored_key.as_slice(), v.as_slice()))
.collect(),
)))
}
pub fn put(&self, txn: &mut RwTxn<'_>, key: &K, value: &V) -> Result<(), Error> {
let encoded = key.encode();
let entry = txn
.table_mut(self.index)
.entry(encoded.into_owned())
.or_default();
if !self.duplicates {
entry.clear();
}
entry.insert(value.encode().to_vec());
Ok(())
}
pub fn delete(&self, txn: &mut RwTxn<'_>, key: &K) -> Result<bool, Error> {
let encoded = key.encode();
Ok(txn.table_mut(self.index).remove(encoded.as_ref()).is_some())
}
pub fn delete_one_duplicate(
&self,
txn: &mut RwTxn<'_>,
key: &K,
value: &V,
) -> Result<bool, Error> {
let encoded = key.encode();
let table = txn.table_mut(self.index);
let Some(values) = table.get_mut(encoded.as_ref()) else {
return Ok(false);
};
let removed = values.remove(value.encode());
if values.is_empty() {
table.remove(encoded.as_ref());
}
Ok(removed)
}
}
pub struct TableIter<'t, K: ?Sized, V: ?Sized> {
pairs: std::vec::IntoIter<(&'t [u8], &'t [u8])>,
marker: TypeMarker<K, V>,
}
impl<'t, K: ?Sized, V: ?Sized> TableIter<'t, K, V> {
fn new(pairs: Vec<(&'t [u8], &'t [u8])>) -> Self {
Self {
pairs: pairs.into_iter(),
marker: PhantomData,
}
}
}
impl<'t, K: Key + ?Sized, V: Val + ?Sized> Iterator for TableIter<'t, K, V> {
type Item = Result<(K::Out<'t>, V::Out<'t>), Error>;
fn next(&mut self) -> Option<Self::Item> {
let (key, value) = self.pairs.next()?;
Some(match K::decode(key) {
Ok(decoded) => Ok((decoded, V::decode(value))),
Err(err) => Err(Error::Storage(err)),
})
}
}
pub struct Storage {
pub env: Env,
pub nodes: Table<u64, [u8]>,
pub edges: Table<u64, [u8]>,
pub out_adj: Table<u64, [u8]>,
pub in_adj: Table<u64, [u8]>,
pub label_idx: Table<[u8], ()>,
pub type_idx: Table<[u8], ()>,
pub node_prop_idx: Table<[u8], ()>,
pub edge_prop_idx: Table<[u8], ()>,
pub fts_postings: Table<[u8], [u8]>,
pub fts_docs: Table<[u8], [u8]>,
pub vectors: Table<u64, [u8]>,
pub meta: Table<str, [u8]>,
}
impl Storage {
pub fn open(_path: &Path, _map_size_gb: usize) -> Result<Self, Error> {
Ok(Self {
env: Env::new(),
nodes: Table::new(T_NODES, false),
edges: Table::new(T_EDGES, false),
out_adj: Table::new(T_OUT_ADJ, true),
in_adj: Table::new(T_IN_ADJ, true),
label_idx: Table::new(T_LABEL_IDX, false),
type_idx: Table::new(T_TYPE_IDX, false),
node_prop_idx: Table::new(T_NODE_PROP_IDX, false),
edge_prop_idx: Table::new(T_EDGE_PROP_IDX, false),
fts_postings: Table::new(T_FTS_POSTINGS, true),
fts_docs: Table::new(T_FTS_DOCS, false),
vectors: Table::new(T_VECTORS, false),
meta: Table::new(T_META, false),
})
}
pub fn copy_to_file(&self, _destination: &Path, _compact: bool) -> Result<(), Error> {
Err(Error::Storage(MemoryError::Unsupported("backup")))
}
pub fn restore_from_file(_snapshot_file: &Path, _dst_dir: &Path) -> Result<(), Error> {
Err(Error::Storage(MemoryError::Unsupported("restore")))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn storage() -> Storage {
Storage::open(Path::new("unused"), 1).expect("in-memory open cannot fail")
}
#[test]
fn u64_keys_iterate_in_numeric_order() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
for id in [300u64, 1, 256, 2] {
s.nodes.put(&mut wtxn, &id, b"x".as_slice()).unwrap();
}
wtxn.commit().unwrap();
let rtxn = s.env.read_txn().unwrap();
let keys: Vec<u64> = s.nodes.iter(&rtxn).unwrap().map(|r| r.unwrap().0).collect();
assert_eq!(keys, vec![1, 2, 256, 300]);
}
#[test]
fn duplicates_are_kept_and_ordered_by_bytes() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
for value in [b"\x02".as_slice(), b"\x00", b"\x01"] {
s.out_adj.put(&mut wtxn, &7u64, value).unwrap();
}
wtxn.commit().unwrap();
let rtxn = s.env.read_txn().unwrap();
let values: Vec<Vec<u8>> = s
.out_adj
.get_duplicates(&rtxn, &7u64)
.unwrap()
.expect("key present")
.map(|r| r.unwrap().1.to_vec())
.collect();
assert_eq!(values, vec![vec![0], vec![1], vec![2]]);
assert_eq!(s.out_adj.len(&rtxn).unwrap(), 3, "each duplicate counts");
}
#[test]
fn a_normal_table_replaces_on_put() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
s.nodes.put(&mut wtxn, &1u64, b"first".as_slice()).unwrap();
s.nodes.put(&mut wtxn, &1u64, b"second".as_slice()).unwrap();
wtxn.commit().unwrap();
let rtxn = s.env.read_txn().unwrap();
assert_eq!(
s.nodes.get(&rtxn, &1u64).unwrap(),
Some(b"second".as_slice())
);
assert_eq!(s.nodes.len(&rtxn).unwrap(), 1);
}
#[test]
fn deleting_one_duplicate_leaves_siblings_then_the_key() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
s.out_adj.put(&mut wtxn, &1u64, b"a".as_slice()).unwrap();
s.out_adj.put(&mut wtxn, &1u64, b"b".as_slice()).unwrap();
assert!(
s.out_adj
.delete_one_duplicate(&mut wtxn, &1u64, b"a".as_slice())
.unwrap()
);
wtxn.commit().unwrap();
let rtxn = s.env.read_txn().unwrap();
assert_eq!(s.out_adj.len(&rtxn).unwrap(), 1);
drop(rtxn);
let mut wtxn = s.env.write_txn().unwrap();
s.out_adj
.delete_one_duplicate(&mut wtxn, &1u64, b"b".as_slice())
.unwrap();
wtxn.commit().unwrap();
let rtxn = s.env.read_txn().unwrap();
assert!(
s.out_adj.get_duplicates(&rtxn, &1u64).unwrap().is_none(),
"the key must go with its last value"
);
}
#[test]
fn dropping_a_write_txn_rolls_everything_back() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
s.nodes.put(&mut wtxn, &1u64, b"kept".as_slice()).unwrap();
s.out_adj.put(&mut wtxn, &1u64, b"kept".as_slice()).unwrap();
wtxn.commit().unwrap();
let mut wtxn = s.env.write_txn().unwrap();
s.nodes
.put(&mut wtxn, &1u64, b"clobbered".as_slice())
.unwrap();
s.nodes.put(&mut wtxn, &2u64, b"added".as_slice()).unwrap();
s.nodes.delete(&mut wtxn, &1u64).unwrap();
s.out_adj
.put(&mut wtxn, &1u64, b"extra".as_slice())
.unwrap();
drop(wtxn);
let rtxn = s.env.read_txn().unwrap();
assert_eq!(
s.nodes.get(&rtxn, &1u64).unwrap(),
Some(b"kept".as_slice()),
"a key mutated twice returns to its pre-transaction value"
);
assert_eq!(s.nodes.get(&rtxn, &2u64).unwrap(), None);
assert_eq!(s.out_adj.len(&rtxn).unwrap(), 1);
}
#[test]
fn prefix_iter_bounds_the_scan() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
for key in [
[0u8, 0, 0, 1, 9].as_slice(),
&[0, 0, 0, 1, 10],
&[0, 0, 0, 2, 11],
] {
s.label_idx.put(&mut wtxn, key, &()).unwrap();
}
wtxn.commit().unwrap();
let rtxn = s.env.read_txn().unwrap();
let found: Vec<Vec<u8>> = s
.label_idx
.prefix_iter(&rtxn, &[0, 0, 0, 1])
.unwrap()
.map(|r| r.unwrap().0.to_vec())
.collect();
assert_eq!(found, vec![vec![0, 0, 0, 1, 9], vec![0, 0, 0, 1, 10]]);
}
#[test]
fn a_write_txn_reads_its_own_writes() {
let s = storage();
let mut wtxn = s.env.write_txn().unwrap();
s.meta.put(&mut wtxn, "k", b"v".as_slice()).unwrap();
assert_eq!(s.meta.get(&wtxn, "k").unwrap(), Some(b"v".as_slice()));
wtxn.commit().unwrap();
}
}