diff options
Diffstat (limited to 'src/store.rs')
| -rw-r--r-- | src/store.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/src/store.rs b/src/store.rs index cb35272..513728e 100644 --- a/src/store.rs +++ b/src/store.rs @@ -301,6 +301,135 @@ pub fn delete_path(doc: &mut AutoCommit, path: &[String], prune: bool) -> Result Ok(()) } +// --- materialization --------------------------------------------------------- + +/// Copies the merged view of `src` into `dst` as fresh objects. +/// +/// * Conflicts collapse exactly as reads resolve them: concurrent maps merge, everything +/// else keeps the newest candidate. +/// * Automerge types survive: text stays text, counters stay counters (normalized to +/// their current value), bytes, timestamps and floats are copied bit-exact. +/// * Map keys are visited in sorted order and list elements in order, so two documents +/// with the same view produce the same operations in the same order. +/// +/// `skip` sees the path of every map entry and list element (list indices as decimal +/// strings); returning true leaves that subtree out. +#[cfg_attr(not(test), allow(dead_code))] // used by compaction from step 4 on +pub(crate) fn materialize_into( + src: &AutoCommit, + dst: &mut AutoCommit, + skip: &dyn Fn(&[String]) -> bool, +) -> Result<(), StoreError> { + let mut path = Vec::new(); + copy_maps( + src, + &[automerge::ROOT], + dst, + &automerge::ROOT, + &mut path, + skip, + ) +} + +fn copy_maps( + src: &AutoCommit, + maps: &[ObjId], + dst: &mut AutoCommit, + target: &ObjId, + path: &mut Vec<String>, + skip: &dyn Fn(&[String]) -> bool, +) -> Result<(), StoreError> { + // merged_keys is a BTreeSet: sorted, so the operation order is deterministic. + for key in merged_keys(src, maps) { + path.push(key.clone()); + if !skip(path) { + if let Some(resolved) = resolve(src, maps, &key, None)? { + copy_resolved(src, &resolved, dst, target, Slot::Key(&key), path, skip)?; + } + } + path.pop(); + } + Ok(()) +} + +fn copy_resolved( + src: &AutoCommit, + resolved: &Resolved, + dst: &mut AutoCommit, + parent: &ObjId, + slot: Slot, + path: &mut Vec<String>, + skip: &dyn Fn(&[String]) -> bool, +) -> Result<(), StoreError> { + match resolved { + Resolved::Maps(maps) => { + let id = put_object(dst, parent, slot, ObjType::Map)?; + copy_maps(src, maps, dst, &id, path, skip) + } + Resolved::Other(Value::Scalar(scalar), _) => { + let scalar = match scalar.as_ref() { + // A read counter carries its increment history; copy only its value, so + // equal counters produce equal operations. + ScalarValue::Counter(counter) => ScalarValue::counter(i64::from(counter.clone())), + other => other.clone(), + }; + put_scalar(dst, parent, slot, scalar) + } + Resolved::Other(Value::Object(obj_type), id) => { + copy_object(src, *obj_type, id, dst, parent, slot, path, skip) + } + } +} + +#[allow(clippy::too_many_arguments)] +fn copy_object( + src: &AutoCommit, + obj_type: ObjType, + id: &ObjId, + dst: &mut AutoCommit, + parent: &ObjId, + slot: Slot, + path: &mut Vec<String>, + skip: &dyn Fn(&[String]) -> bool, +) -> Result<(), StoreError> { + match obj_type { + ObjType::Map => copy_resolved( + src, + &Resolved::Maps(vec![id.clone()]), + dst, + parent, + slot, + path, + skip, + ), + ObjType::List => { + let list = put_object(dst, parent, slot, ObjType::List)?; + // Skipped elements leave no gap in the copy. + let mut position = 0; + for (index, item) in src.list_range(id, ..).enumerate() { + path.push(index.to_string()); + if !skip(path) { + let child_id = item.id(); + let child = match Value::from(item.value).into_owned() { + Value::Object(ObjType::Map) => Resolved::Maps(vec![child_id]), + value => Resolved::Other(value, child_id), + }; + copy_resolved(src, &child, dst, &list, Slot::Index(position), path, skip)?; + position += 1; + } + path.pop(); + } + Ok(()) + } + ObjType::Text => { + let text = put_object(dst, parent, slot, ObjType::Text)?; + dst.splice_text(&text, 0, 0, &src.text(id)?)?; + Ok(()) + } + ObjType::Table => Err(anyhow!("Automerge tables are not supported").into()), + } +} + /// Where a value is placed inside its parent object. #[derive(Clone, Copy)] enum Slot<'a> { |
