mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 21:07:43 +00:00
feat: allow users to query the changed containers in the target id range (#549)
* feat: allow users to query the changed containers in the target id range * chore: add changeset note * chore: update cargo toml * test: add related tests and add a commit before get_changed_container_in
This commit is contained in:
parent
6e878d216a
commit
778ca5452d
9 changed files with 122 additions and 7 deletions
5
.changeset/hip-squids-train.md
Normal file
5
.changeset/hip-squids-train.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"loro-crdt": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Feat: allow users to query the changed containers in the target id range
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1069,6 +1069,7 @@ dependencies = [
|
||||||
"ctor 0.2.6",
|
"ctor 0.2.6",
|
||||||
"dev-utils",
|
"dev-utils",
|
||||||
"enum-as-inner 0.6.0",
|
"enum-as-inner 0.6.0",
|
||||||
|
"fxhash",
|
||||||
"generic-btree",
|
"generic-btree",
|
||||||
"loro-common 1.0.0-beta.5",
|
"loro-common 1.0.0-beta.5",
|
||||||
"loro-delta 1.0.0-beta.5",
|
"loro-delta 1.0.0-beta.5",
|
||||||
|
|
|
@ -1653,6 +1653,18 @@ impl LoroDoc {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
|
||||||
|
self.commit_then_renew();
|
||||||
|
let mut set = FxHashSet::default();
|
||||||
|
let oplog = &self.oplog().try_lock().unwrap();
|
||||||
|
for op in oplog.iter_ops(id.to_span(len)) {
|
||||||
|
let id = oplog.arena.get_container_id(op.container()).unwrap();
|
||||||
|
set.insert(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: PERF: This method is quite slow because it iterates all the changes
|
// FIXME: PERF: This method is quite slow because it iterates all the changes
|
||||||
|
|
|
@ -328,6 +328,10 @@ impl<'a> RichOp<'a> {
|
||||||
self.peer
|
self.peer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn container(&self) -> ContainerIdx {
|
||||||
|
self.op.container
|
||||||
|
}
|
||||||
|
|
||||||
pub fn timestamp(&self) -> i64 {
|
pub fn timestamp(&self) -> i64 {
|
||||||
self.timestamp
|
self.timestamp
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,6 +190,8 @@ extern "C" {
|
||||||
pub type JsCommitOption;
|
pub type JsCommitOption;
|
||||||
#[wasm_bindgen(typescript_type = "ImportStatus")]
|
#[wasm_bindgen(typescript_type = "ImportStatus")]
|
||||||
pub type JsImportStatus;
|
pub type JsImportStatus;
|
||||||
|
#[wasm_bindgen(typescript_type = "(change: ChangeMeta) => boolean")]
|
||||||
|
pub type JsTravelChangeFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod observer {
|
mod observer {
|
||||||
|
@ -606,7 +608,15 @@ impl LoroDoc {
|
||||||
/// @param ids - the changes to visit
|
/// @param ids - the changes to visit
|
||||||
/// @param f - the callback function, return `true` to continue visiting, return `false` to stop
|
/// @param f - the callback function, return `true` to continue visiting, return `false` to stop
|
||||||
#[wasm_bindgen(js_name = "travelChangeAncestors")]
|
#[wasm_bindgen(js_name = "travelChangeAncestors")]
|
||||||
pub fn travel_change_ancestors(&self, ids: Vec<JsID>, f: js_sys::Function) -> JsResult<()> {
|
pub fn travel_change_ancestors(
|
||||||
|
&self,
|
||||||
|
ids: Vec<JsID>,
|
||||||
|
f: JsTravelChangeFunction,
|
||||||
|
) -> JsResult<()> {
|
||||||
|
let f: js_sys::Function = match f.dyn_into::<js_sys::Function>() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => return Err(JsValue::from_str("Expected a function")),
|
||||||
|
};
|
||||||
let observer = observer::Observer::new(f);
|
let observer = observer::Observer::new(f);
|
||||||
self.0
|
self.0
|
||||||
.travel_change_ancestors(
|
.travel_change_ancestors(
|
||||||
|
@ -1652,6 +1662,30 @@ impl LoroDoc {
|
||||||
)?;
|
)?;
|
||||||
Ok(JsValue::from(obj).into())
|
Ok(JsValue::from(obj).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets container IDs modified in the given ID range.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This method will implicitly commit.
|
||||||
|
///
|
||||||
|
/// This method identifies which containers were affected by changes in a given range of operations.
|
||||||
|
/// It can be used together with `doc.travelChangeAncestors()` to analyze the history of changes
|
||||||
|
/// and determine which containers were modified by each change.
|
||||||
|
///
|
||||||
|
/// @param id - The starting ID of the change range
|
||||||
|
/// @param len - The length of the change range to check
|
||||||
|
/// @returns An array of container IDs that were modified in the given range
|
||||||
|
pub fn getChangedContainersIn(&self, id: JsID, len: usize) -> JsResult<Vec<JsContainerID>> {
|
||||||
|
let id = js_id_to_id(id)?;
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.get_changed_containers_in(id, len)
|
||||||
|
.into_iter()
|
||||||
|
.map(|cid| {
|
||||||
|
let v: JsValue = (&cid).into();
|
||||||
|
v.into()
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -3271,16 +3305,12 @@ impl LoroMovableList {
|
||||||
|
|
||||||
/// Get the last mover of the list item at the given position.
|
/// Get the last mover of the list item at the given position.
|
||||||
pub fn getLastMoverAt(&self, pos: usize) -> Option<JsStrPeerID> {
|
pub fn getLastMoverAt(&self, pos: usize) -> Option<JsStrPeerID> {
|
||||||
self.handler
|
self.handler.get_last_mover_at(pos).map(peer_id_to_js)
|
||||||
.get_last_mover_at(pos)
|
|
||||||
.map(peer_id_to_js)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the last editor of the list item at the given position.
|
/// Get the last editor of the list item at the given position.
|
||||||
pub fn getLastEditorAt(&self, pos: usize) -> Option<JsStrPeerID> {
|
pub fn getLastEditorAt(&self, pos: usize) -> Option<JsStrPeerID> {
|
||||||
self.handler
|
self.handler.get_last_editor_at(pos).map(peer_id_to_js)
|
||||||
.get_last_editor_at(pos)
|
|
||||||
.map(peer_id_to_js)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
encodeFrontiers,
|
encodeFrontiers,
|
||||||
decodeFrontiers,
|
decodeFrontiers,
|
||||||
} from "../bundler/index";
|
} from "../bundler/index";
|
||||||
|
import { ContainerID } from "loro-wasm";
|
||||||
|
|
||||||
it("basic example", () => {
|
it("basic example", () => {
|
||||||
const doc = new LoroDoc();
|
const doc = new LoroDoc();
|
||||||
|
@ -675,3 +676,16 @@ it("can push container to movable list", () => {
|
||||||
const map = list.pushContainer(new LoroMap());
|
const map = list.pushContainer(new LoroMap());
|
||||||
expect(list.toJSON()).toStrictEqual([{}]);
|
expect(list.toJSON()).toStrictEqual([{}]);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("can query the history for changed containers", () => {
|
||||||
|
const doc = new LoroDoc();
|
||||||
|
doc.setPeerId("0");
|
||||||
|
doc.getText("text").insert(0, "H");
|
||||||
|
doc.getMap("map").set("key", "H");
|
||||||
|
const changed = doc.getChangedContainersIn({ peer: "0", counter: 0 }, 2)
|
||||||
|
const changedSet = new Set(changed);
|
||||||
|
expect(changedSet).toEqual(new Set([
|
||||||
|
"cid:root-text:Text" as ContainerID,
|
||||||
|
"cid:root-map:Map" as ContainerID,
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
|
|
@ -21,6 +21,7 @@ delta = { path = "../delta", package = "loro-delta", version = "1.0.0-beta.5" }
|
||||||
generic-btree = { version = "^0.10.5" }
|
generic-btree = { version = "^0.10.5" }
|
||||||
enum-as-inner = { workspace = true }
|
enum-as-inner = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
fxhash = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0.87"
|
serde_json = "1.0.87"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![warn(missing_debug_implementations)]
|
#![warn(missing_debug_implementations)]
|
||||||
use event::{DiffEvent, Subscriber};
|
use event::{DiffEvent, Subscriber};
|
||||||
|
use fxhash::FxHashSet;
|
||||||
use loro_common::InternalString;
|
use loro_common::InternalString;
|
||||||
pub use loro_internal::cursor::CannotFindRelativePosition;
|
pub use loro_internal::cursor::CannotFindRelativePosition;
|
||||||
use loro_internal::cursor::Cursor;
|
use loro_internal::cursor::Cursor;
|
||||||
|
@ -848,6 +849,21 @@ impl LoroDoc {
|
||||||
pub fn is_shallow(&self) -> bool {
|
pub fn is_shallow(&self) -> bool {
|
||||||
self.doc.is_shallow()
|
self.doc.is_shallow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets container IDs modified in the given ID range.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This method will implicitly commit.
|
||||||
|
///
|
||||||
|
/// This method can be used in conjunction with `doc.travel_change_ancestors()` to traverse
|
||||||
|
/// the history and identify all changes that affected specific containers.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `id` - The starting ID of the change range
|
||||||
|
/// * `len` - The length of the change range to check
|
||||||
|
pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
|
||||||
|
self.doc.get_changed_containers_in(id, len)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// It's used to prevent the user from implementing the trait directly.
|
/// It's used to prevent the user from implementing the trait directly.
|
||||||
|
|
|
@ -2190,3 +2190,35 @@ fn get_editor() {
|
||||||
let mov_id = tree.get_last_move_id(&node_0).unwrap();
|
let mov_id = tree.get_last_move_id(&node_0).unwrap();
|
||||||
assert_eq!(mov_id.peer, 2);
|
assert_eq!(mov_id.peer, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_changed_containers_in() {
|
||||||
|
let doc = LoroDoc::new();
|
||||||
|
doc.set_peer_id(0).unwrap();
|
||||||
|
let text = doc.get_text("text");
|
||||||
|
text.insert(0, "H").unwrap();
|
||||||
|
let map = doc.get_map("map");
|
||||||
|
map.insert("key", "value").unwrap();
|
||||||
|
let changed_set = doc.get_changed_containers_in(ID::new(0, 0), 2);
|
||||||
|
assert_eq!(
|
||||||
|
changed_set,
|
||||||
|
vec![
|
||||||
|
ContainerID::new_root("text", ContainerType::Text),
|
||||||
|
ContainerID::new_root("map", ContainerType::Map),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert("key1", "value1").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
doc.get_deep_value().to_json_value(),
|
||||||
|
json!({
|
||||||
|
"text": "H",
|
||||||
|
"map": {
|
||||||
|
"key": "value",
|
||||||
|
"key1": "value1"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue