From 11af19a5f6f45ba57b990b53af4d0e241c99895c Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Mon, 23 Sep 2024 11:33:15 +0800 Subject: [PATCH] fix: cursor behavior when using gc-snapshot --- crates/loro-internal/src/loro.rs | 10 +++- crates/loro/tests/integration_test/gc_test.rs | 59 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index 0e985cb7..bc871161 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -1360,6 +1360,10 @@ impl LoroDoc { .ok_or(CannotFindRelativePosition::ContainerDeleted)?; // We know where the target id is when we trace back to the delete_op_id. let Some(delete_op_id) = find_last_delete_op(&oplog, id, idx) else { + if oplog.trimmed_vv().includes_id(id) { + return Err(CannotFindRelativePosition::HistoryCleared); + } + tracing::error!("Cannot find id {}", id); return Err(CannotFindRelativePosition::IdNotFound); }; @@ -1557,8 +1561,12 @@ impl LoroDoc { } } +// FIXME: PERF: This method is quite slow because it iterates all the changes fn find_last_delete_op(oplog: &OpLog, id: ID, idx: ContainerIdx) -> Option { - let start_vv = oplog.dag.frontiers_to_vv(&id.into())?; + let start_vv = oplog + .dag + .frontiers_to_vv(&id.into()) + .unwrap_or_else(|| oplog.trimmed_vv().to_vv()); for change in oplog.iter_changes_causally_rev(&start_vv, oplog.vv()) { for op in change.ops.iter().rev() { if op.container != idx { diff --git a/crates/loro/tests/integration_test/gc_test.rs b/crates/loro/tests/integration_test/gc_test.rs index d1a06ef2..31aa8d79 100644 --- a/crates/loro/tests/integration_test/gc_test.rs +++ b/crates/loro/tests/integration_test/gc_test.rs @@ -4,7 +4,7 @@ use std::{ }; use super::gen_action; -use loro::{ExportMode, Frontiers, LoroDoc, ID}; +use loro::{cursor::CannotFindRelativePosition, ExportMode, Frontiers, LoroDoc, ID}; #[test] fn test_gc() -> anyhow::Result<()> { @@ -240,3 +240,60 @@ fn the_vv_on_gc_doc() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_cursor_that_cannot_be_found_when_exporting_gc_snapshot() -> anyhow::Result<()> { + let doc = LoroDoc::new(); + doc.set_peer_id(1)?; + doc.get_text("text").insert(0, "Hello world")?; + let c = doc + .get_text("text") + .get_cursor(3, loro::cursor::Side::Left) + .unwrap(); + doc.get_text("text").delete(0, 5)?; + doc.commit(); + let snapshot = doc.export(loro::ExportMode::gc_snapshot(&doc.oplog_frontiers())); + let new_doc = LoroDoc::new(); + new_doc.import(&snapshot)?; + let result = new_doc.get_cursor_pos(&c); + match result { + Ok(v) => { + dbg!(v); + unreachable!() + } + Err(CannotFindRelativePosition::HistoryCleared) => {} + Err(x) => { + dbg!(x); + unreachable!() + } + } + Ok(()) +} + +#[test] +fn test_cursor_that_can_be_found_when_exporting_gc_snapshot() -> anyhow::Result<()> { + let doc = LoroDoc::new(); + doc.set_peer_id(1)?; + doc.get_text("text").insert(0, "Hello world")?; + doc.commit(); + let c = doc + .get_text("text") + .get_cursor(3, loro::cursor::Side::Left) + .unwrap(); + doc.get_text("text").delete(0, 5)?; + doc.commit(); + let snapshot = doc.export(loro::ExportMode::gc_snapshot_from_id(ID::new(1, 10))); + let new_doc = LoroDoc::new(); + new_doc.import(&snapshot)?; + let result = new_doc.get_cursor_pos(&c); + match result { + Ok(v) => { + assert_eq!(v.current.pos, 0); + } + Err(x) => { + dbg!(x); + unreachable!() + } + } + Ok(()) +}