use yo_common::Result;
use yo_doc::{Doc, Docs, IndexKind, Key, Keys, Value};
#[derive(Debug, Default)]
pub struct Props {
docs: Docs,
}
#[must_use]
pub fn id_key(id: u64) -> [u8; 8] {
id.to_be_bytes()
}
impl Props {
pub fn new() -> Props {
Props { docs: Docs::new() }
}
pub fn put(&mut self, id: u64, doc: &[u8]) -> Result<bool> {
self.docs.put_bytes(&id_key(id), doc)
}
pub fn put_value(&mut self, id: u64, value: Value<'_>) -> Result<bool> {
self.docs.put(&id_key(id), value)
}
#[must_use]
pub fn get(&self, id: u64) -> Option<Doc<'_>> {
self.docs.get(&id_key(id))
}
#[must_use]
pub fn bytes(&self, id: u64) -> Option<&[u8]> {
self.docs.bytes(&id_key(id))
}
#[must_use]
pub fn contains(&self, id: u64) -> bool {
self.docs.contains(&id_key(id))
}
pub fn remove(&mut self, id: u64) -> bool {
self.docs.remove(&id_key(id))
}
#[must_use]
pub fn len(&self) -> usize {
self.docs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.docs.is_empty()
}
pub fn create_index(&mut self, path: &str, kind: IndexKind) -> Result<()> {
self.docs.create_index_bytes(path.as_bytes(), kind)
}
pub fn drop_index(&mut self, path: &str) -> bool {
self.docs.drop_index(path)
}
pub fn find(&self, path: &str, key: &Key, mut f: impl FnMut(u64, Doc<'_>)) -> Result<usize> {
self.docs.find(path, key, |id, doc| {
if let Some(id) = read_key(id) {
f(id, doc);
}
})
}
pub fn count(&self, path: &str, key: &Key) -> Result<usize> {
self.docs.count(path, key)
}
pub fn iter(&self) -> impl Iterator<Item = (u64, Doc<'_>)> {
self.docs
.iter()
.filter_map(|(id, doc)| read_key(id).map(|id| (id, doc)))
}
#[must_use]
pub fn keys(&self) -> &Keys {
self.docs.keys()
}
pub fn clear(&mut self) {
self.docs.clear();
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.docs.memory_bytes()
}
#[must_use]
pub fn docs(&self) -> &Docs {
&self.docs
}
}
fn read_key(k: &[u8]) -> Option<u64> {
let raw: [u8; 8] = k.try_into().ok()?;
Some(u64::from_be_bytes(raw))
}
#[cfg(test)]
mod tests {
use super::*;
use yo_doc::Builder;
fn person(name: &str, city: &str, age: i64) -> Vec<u8> {
let mut b = Builder::new();
b.begin_object().unwrap();
b.key(b"age").unwrap();
b.int(age).unwrap();
b.key(b"city").unwrap();
b.text(city).unwrap();
b.key(b"name").unwrap();
b.text(name).unwrap();
b.end_object().unwrap();
b.finish().unwrap().to_vec()
}
#[test]
fn a_node_keeps_its_properties() {
let mut p = Props::new();
assert!(p.put(1, &person("ada", "london", 36)).unwrap());
assert!(
!p.put(1, &person("ada", "turin", 37)).unwrap(),
"an overwrite is not new"
);
assert_eq!(p.len(), 1);
let got = p.get(1).expect("stored");
assert_eq!(got.get(b"city").and_then(|c| c.as_text()), Some("turin"));
assert_eq!(got.get(b"age").and_then(|a| a.as_int()), Some(37));
}
#[test]
fn an_id_that_was_never_written_has_nothing() {
let mut p = Props::new();
p.put(1, &person("ada", "london", 36)).unwrap();
assert!(p.get(2).is_none());
assert!(!p.contains(2));
assert!(!p.remove(2));
assert!(p.remove(1));
assert!(p.is_empty());
}
#[test]
fn the_whole_range_of_a_u64_id_works() {
let ids = [
0u64,
1,
u64::from(u32::MAX) - 1,
u64::from(u32::MAX),
u64::from(u32::MAX) + 1,
u64::MAX,
];
let mut p = Props::new();
for (i, id) in ids.into_iter().enumerate() {
assert!(
p.put(id, &person(&format!("n{i}"), "here", i as i64))
.unwrap()
);
}
assert_eq!(p.len(), ids.len());
for (i, id) in ids.into_iter().enumerate() {
let got = p.get(id).unwrap_or_else(|| panic!("{id} is missing"));
assert_eq!(
got.get(b"name").and_then(|n| n.as_text()),
Some(&*format!("n{i}"))
);
}
}
#[test]
fn an_index_finds_nodes_by_a_property() {
let mut p = Props::new();
p.create_index("$.city", IndexKind::Equality).unwrap();
p.put(1, &person("ada", "london", 36)).unwrap();
p.put(2, &person("grace", "london", 45)).unwrap();
p.put(3, &person("edsger", "austin", 51)).unwrap();
let mut found = Vec::new();
let n = p
.find("$.city", &Key::text("london"), |id, _| found.push(id))
.unwrap();
assert_eq!(n, 2);
found.sort_unstable();
assert_eq!(found, vec![1, 2]);
assert_eq!(p.count("$.city", &Key::text("austin")).unwrap(), 1);
}
#[test]
fn an_index_declared_after_the_fact_backfills() {
let mut p = Props::new();
p.put(1, &person("ada", "london", 36)).unwrap();
p.put(2, &person("grace", "london", 45)).unwrap();
p.create_index("$.city", IndexKind::Equality).unwrap();
assert_eq!(p.count("$.city", &Key::text("london")).unwrap(), 2);
assert!(p.remove(1));
assert_eq!(p.count("$.city", &Key::text("london")).unwrap(), 1);
}
#[test]
fn the_field_names_are_stored_once() {
let mut p = Props::new();
for id in 0..100u64 {
p.put(id, &person("someone", "london", id as i64)).unwrap();
}
assert_eq!(p.keys().len(), 3);
assert!(p.get(7).expect("stored").value().is_interned());
}
#[test]
fn iterating_gives_back_the_ids_that_went_in() {
let mut p = Props::new();
for id in [5u64, 9, 41_920] {
p.put(id, &person("someone", "london", 1)).unwrap();
}
let mut ids: Vec<u64> = p.iter().map(|(id, _)| id).collect();
ids.sort_unstable();
assert_eq!(ids, vec![5, 9, 41_920]);
}
}