diff options
| author | tv <tv@krebsco.de> | 2026-11-01 22:29:54 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-01 22:29:54 +0100 |
| commit | 3e115a7e9cc5846d6c1025a7297a006c2d64709b (patch) | |
| tree | 56ec60d608884c2b19d9604643a7ec6df54f5c9c /src/document | |
| parent | 1b9239c9144572784dca6d49b92179b2cb57108e (diff) | |
api: add support for counters
Diffstat (limited to 'src/document')
| -rw-r--r-- | src/document/store.rs | 57 |
1 files changed, 57 insertions, 0 deletions
diff --git a/src/document/store.rs b/src/document/store.rs index c16767b..13824b9 100644 --- a/src/document/store.rs +++ b/src/document/store.rs @@ -94,6 +94,9 @@ pub(crate) enum StoreError { /// An index past a list's end (`length` items). #[error("index {index} is past the list's end ({length} items)")] PastTheEnd { index: usize, length: usize }, + /// An increment of something that isn't a counter (`increment_path`). + #[error("not a counter")] + NotACounter, /// A read at heads the document doesn't have (`at::At`). #[error("this node doesn't have these heads (yet)")] UnknownHeads, @@ -744,6 +747,60 @@ fn parent_for_write(doc: &mut impl Transactable, parents: &[String]) -> Result<P Ok(current) } +/// Adds `by` to the counter at `path`, creating one with `by` where there is nothing +/// (in a map: a list's items are only incremented); returns its value then. Increments +/// of one counter on several nodes add up, where writes of numbers would leave one. +/// Anything else there (the newest of concurrent values, as a read resolves them) is +/// `NotACounter`. +pub(crate) fn increment_path( + doc: &mut impl Transactable, + path: &[String], + by: i64, +) -> Result<i64, StoreError> { + let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?; + let (parent, prop): (ObjId, Prop) = match parent_for_write(doc, parents)? { + Parent::Maps(maps) => { + let holds = candidates(doc, &maps, leaf)?; + if holds.is_empty() { + doc.put(&maps[0], leaf.as_str(), ScalarValue::counter(by))?; + return Ok(by); + } + // The map that holds the newest value. + let newest = &holds[0].1; + let map = maps + .iter() + .find(|map| { + doc.get_all(*map, leaf.as_str()) + .is_ok_and(|all| all.iter().any(|(_, id)| id == newest)) + }) + .cloned() + .ok_or(StoreError::NotFound)?; + (map, Prop::Map(leaf.clone())) + } + Parent::List(list) => { + let index = list_index(leaf).ok_or(StoreError::NotFound)?; + if index >= doc.length(&list) { + return Err(StoreError::NotFound); + } + (list, Prop::Seq(index)) + } + }; + counter_at(doc, &parent, prop.clone())?; + doc.increment(&parent, prop.clone(), by)?; + counter_at(doc, &parent, prop) +} + +/// The value of the counter `prop` of `parent` holds (the newest of its values). +fn counter_at(doc: &impl ReadDoc, parent: &ObjId, prop: Prop) -> Result<i64, StoreError> { + match doc.get(parent, prop)? { + Some((Value::Scalar(scalar), _)) => match scalar.as_ref() { + ScalarValue::Counter(counter) => Ok(i64::from(counter)), + _ => Err(StoreError::NotACounter), + }, + _ => Err(StoreError::NotACounter), + } +} + /// Inserts `value` into a list: `path` ends in the index it goes to (the items from /// there on move up one; the list's length appends). An index past the end is /// `PastTheEnd`; a parent that isn't a list `NotAList`; anything but an index there, or |
