pub struct OpMove<ID: TreeId, TM: TreeMeta, A: Actor> { /* private fields */ }Expand description
Implements OpMove, the only way to manipulate tree data.
OpMove are applied via State::apply_op() or at a higher
level via TreeReplica::apply_op()
§From the paper[1]:
We allow the tree to be updated in three ways: by creating a new child of any parent node, by deleting a node, or by moving a node to be a child of a new parent. However all three types of update can be represented by a move operation. To create a node, we generate a fresh ID for that node, and issue an operation to move this new ID to be created. We also designate as “trash” some node ID that does not exist in the tree; then we can delete a node by moving it to be a child of the trash.
Thus, we define one kind of operation: Move t p m c. A move operation is a 4-tuple consisting of a timestamp t of type ’t, a parent node ID p of type ’n, a metadata field m of type ’m, and a child node ID c of type ’n. Here, ’t, ’n, and ’m are type variables that can be replaced with arbitrary types; we only require that node identifiers ’n are globally unique (eg UUIDs); timestamps ’t need to be globally unique and totally ordered (eg Lamport timestamps [11]).
The meaning of an operation Move t p m c is that at time t, the node with ID c is moved to be a child of the parent node with ID p. The operation does not specify the old location of c; the algorithm simply removes c from wherever it is currently located in the tree, and moves it to p. If c does not currently exist in the tree, it is created as a child of p.
The metadata field m in a move operation allows additional information to be associated with the parent-child relationship of p and c. For example, in a filesystem, the parent and child are the inodes of a directory and a file within it respectively, and the metadata contains the filename of the child. Thus, a file with inode c can be renamed by performing a Move t p m c, where the new parent directory p is the inode of the existing parent (unchanged), but the metadata m contains the new filename.
§When users want to make changes to the tree on their local replica they generate new Move t p m c operations for these changes, and apply these operations using the algorithm described…
[1] https://martin.kleppmann.com/papers/move-op.pdf
Implementations§
Source§impl<ID: TreeId, TM: TreeMeta, A: Actor> OpMove<ID, TM, A>
impl<ID: TreeId, TM: TreeMeta, A: Actor> OpMove<ID, TM, A>
Sourcepub fn new(
timestamp: Clock<A>,
parent_id: ID,
metadata: TM,
child_id: ID,
) -> Self
pub fn new( timestamp: Clock<A>, parent_id: ID, metadata: TM, child_id: ID, ) -> Self
create a new OpMove instance
Sourcepub fn timestamp(&self) -> &Clock<A>
pub fn timestamp(&self) -> &Clock<A>
returns timestamp reference
Examples found in repository?
278fn demo_move_to_trash() {
279 // pass true flag to enable causally stable threshold tracking
280 let mut r1: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
281 let mut r2: TreeReplica<TypeId, TypeMeta, TypeActor> = TreeReplica::new(new_id());
282
283 let ids: HashMap<&str, TypeId> = [
284 ("forest", new_id()),
285 ("trash", new_id()),
286 ("root", new_id()),
287 ("home", new_id()),
288 ("bob", new_id()),
289 ("project", new_id()),
290 ]
291 .iter()
292 .cloned()
293 .collect();
294
295 // Generate initial tree state.
296 //
297 // - forest
298 // - trash
299 // - root
300 // - home
301 // - bob
302 // - project
303 let mut ops = vec![
304 (ids["forest"], "root", ids["root"]),
305 (ids["forest"], "trash", ids["trash"]),
306 (ids["root"], "home", ids["home"]),
307 (ids["home"], "bob", ids["bob"]),
308 (ids["bob"], "project", ids["project"]),
309 ];
310
311 // add some nodes under project
312 mktree_ops(&mut ops, &mut r1, ids["project"], 2, 3);
313 let opmoves = r1.opmoves(ops);
314 r1.apply_ops_byref(&opmoves);
315 r2.apply_ops_byref(&opmoves);
316
317 println!("Initial tree");
318 print_tree(r1.tree(), &ids["forest"]);
319
320 // move project to trash
321 let ops = vec![r1.opmove(ids["trash"], "project", ids["project"])];
322 r1.apply_ops_byref(&ops);
323 r2.apply_ops_byref(&ops);
324
325 println!("\nAfter project moved to trash (deleted) on both replicas");
326 print_tree(r1.tree(), &ids["forest"]);
327
328 // Initially, trashed nodes must be retained because a concurrent move
329 // operation may move them back out of the trash.
330 //
331 // Once the operation that moved a node to the trash is causally
332 // stable, we know that no future operations will refer to this node,
333 // and so the trashed node and its descendants can be discarded.
334 //
335 // note: change r1.opmoves() to r2.opmoves() above to
336 // make the causally stable threshold less than the trash operation
337 // timestamp, which will cause this test to fail, ie hit the
338 // "trash should not be emptied" condition.
339 let result = r2.causally_stable_threshold();
340 match result {
341 Some(cst) if cst < ops[0].timestamp() => {
342 println!(
343 "\ncausally stable threshold:\n{:#?}\n\ntrash operation:\n{:#?}",
344 cst,
345 ops[0].timestamp()
346 );
347 panic!("!error: causally stable threshold is less than trash operation timestamp");
348 }
349 None => panic!("!error: causally stable threshold not found"),
350 _ => {}
351 }
352
353 // empty trash
354 r1.tree_mut().rm_subtree(&ids["trash"], false);
355 println!("\nDelete op is now causally stable, so we can empty trash:");
356 print_tree(r1.tree(), &ids["forest"]);
357}Trait Implementations§
Source§impl<ID: TreeId + Arbitrary, A: Actor + Arbitrary, TM: TreeMeta + Arbitrary> Arbitrary for OpMove<ID, TM, A>
impl<ID: TreeId + Arbitrary, A: Actor + Arbitrary, TM: TreeMeta + Arbitrary> Arbitrary for OpMove<ID, TM, A>
Source§impl<'de, ID, TM, A> Deserialize<'de> for OpMove<ID, TM, A>
impl<'de, ID, TM, A> Deserialize<'de> for OpMove<ID, TM, A>
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl<ID: PartialEq + TreeId, TM: PartialEq + TreeMeta, A: PartialEq + Actor> PartialEq for OpMove<ID, TM, A>
impl<ID: PartialEq + TreeId, TM: PartialEq + TreeMeta, A: PartialEq + Actor> PartialEq for OpMove<ID, TM, A>
impl<ID: Eq + TreeId, TM: Eq + TreeMeta, A: Eq + Actor> Eq for OpMove<ID, TM, A>
impl<ID: TreeId, TM: TreeMeta, A: Actor> StructuralPartialEq for OpMove<ID, TM, A>
Auto Trait Implementations§
impl<ID, TM, A> Freeze for OpMove<ID, TM, A>
impl<ID, TM, A> RefUnwindSafe for OpMove<ID, TM, A>
impl<ID, TM, A> Send for OpMove<ID, TM, A>
impl<ID, TM, A> Sync for OpMove<ID, TM, A>
impl<ID, TM, A> Unpin for OpMove<ID, TM, A>
impl<ID, TM, A> UnwindSafe for OpMove<ID, TM, A>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more