Skip to content

Commit 8d4437b

Browse files
authored
fix(storage): RGA insert_str now correctly respects position parameter (#1593)
1 parent c0c4d8a commit 8d4437b

5 files changed

Lines changed: 41 additions & 10 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/dag/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
[package]
22
name = "calimero-dag"
3+
authors.workspace = true
34
version.workspace = true
45
edition.workspace = true
56
repository.workspace = true
67
license.workspace = true
8+
description.workspace = true
9+
publish = true
710

811
[dependencies]
912
async-trait.workspace = true

crates/storage/src/collections/rga.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,13 @@ impl<S: StorageAdaptor> ReplicatedGrowableArray<S> {
342342
break;
343343
}
344344
} else {
345-
// Sort by CharId (HLC timestamp) for deterministic order
346-
candidates.sort_by_key(|(id, _)| *id);
345+
// Sort by CharId in REVERSE order (latest timestamp first)
346+
// This ensures sequential mid-document insertions are placed correctly:
347+
// When inserting at position 6 in "Hello World", the new characters
348+
// should come BEFORE 'W', not after it, even though they have the same left neighbor.
349+
candidates.sort_by_key(|(id, _)| std::cmp::Reverse(*id));
347350

348-
// Take the character with lowest CharId (earliest timestamp)
351+
// Take the character with highest CharId (latest timestamp)
349352
let (next_id, next_char) = candidates[0];
350353
ordered.push((*next_id, next_char.clone()));
351354
current_left = *next_id;

crates/storage/src/tests/rga.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ fn test_rga_basic_insert() {
1313
rga.insert(1, 'i').unwrap();
1414
assert_eq!(rga.get_text().unwrap(), "Hi");
1515

16-
// Insert at position 0 - both 'H' and '!' have left=root
17-
// RGA orders by HLC timestamp: 'H' (earlier) comes before '!' (later)
16+
// Insert at position 0 (before everything)
17+
// Both 'H' and '!' have left=root, but '!' has later timestamp
18+
// With REVERSED sort (latest first), '!' comes BEFORE 'H' - correct for sequential edits!
1819
rga.insert(0, '!').unwrap();
19-
// '!' has left=root, later timestamp than 'H', so comes after 'H'
2020
let text = rga.get_text().unwrap();
21-
assert!(text.starts_with('H')); // 'H' was first, has earliest timestamp
22-
assert_eq!(text.len(), 3);
21+
assert_eq!(text, "!Hi", "Insert at position 0 should prepend");
2322
}
2423

2524
#[test]
@@ -71,6 +70,32 @@ fn test_rga_insert_str_middle() {
7170
assert_eq!(text.len(), 21); // "Hello" + " Beautiful" + " World"
7271
}
7372

73+
#[test]
74+
fn test_rga_insert_str_position_bug() {
75+
env::reset_for_testing();
76+
77+
let mut rga = ReplicatedGrowableArray::new();
78+
79+
// Insert "Hello World" as a single operation
80+
rga.insert_str(0, "Hello World").unwrap();
81+
assert_eq!(rga.get_text().unwrap(), "Hello World");
82+
83+
// Insert "Beautiful " at position 6 (after "Hello ", before "World")
84+
// Position 6 should be right before 'W'
85+
rga.insert_str(6, "Beautiful ").unwrap();
86+
let result = rga.get_text().unwrap();
87+
88+
eprintln!("Result: '{}'", result);
89+
eprintln!("Expected: 'Hello Beautiful World'");
90+
91+
// Expected: "Hello Beautiful World"
92+
assert_eq!(
93+
result, "Hello Beautiful World",
94+
"insert_str at position 6 should insert before 'World', got: '{}'",
95+
result
96+
);
97+
}
98+
7499
#[test]
75100
fn test_rga_delete_range() {
76101
env::reset_for_testing();

crates/version/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "calimero-version"
3-
version = "0.10.0-rc.8"
3+
version = "0.10.0-rc.9"
44
authors.workspace = true
55
edition.workspace = true
66
repository.workspace = true

0 commit comments

Comments
 (0)