Skip to content

Commit 72924f7

Browse files
committed
fix case where we do not properly "resurrect" old rows on re-inertion
1 parent 0b074d9 commit 72924f7

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed

core/rs/core/src/changes_vtab_write.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ unsafe fn merge_insert(
629629
// The current node might have missed the delete preceeding this causal length
630630
// in out-of-order delivery setups but we still call it a resurrect as special
631631
// handling needs to happen in the "alive -> missed_delete -> alive" case.
632-
let needs_resurrect = insert_cl > local_cl && insert_col_vrsn % 2 == 1;
632+
let needs_resurrect = insert_cl > local_cl && insert_cl % 2 == 1;
633633
let row_exists_locally = local_cl != 0;
634634
let is_sentinel_only = crate::c::INSERT_SENTINEL == insert_col;
635635

py/correctness/tests/test_cl_merging.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ def sync_left_to_right(l, r, since):
4545
r.commit()
4646

4747

48+
def sync_left_to_right_exact_version(l, r, db_version):
49+
changes = l.execute(
50+
"SELECT * FROM crsql_changes WHERE db_version = ?", (db_version,))
51+
for change in changes:
52+
r.execute(
53+
"INSERT INTO crsql_changes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", change)
54+
r.commit()
55+
56+
4857
def sync_left_to_right_include_siteid(l, r, since):
4958
changes = l.execute(
5059
"SELECT [table], pk, cid, val, col_version, db_version, coalesce(site_id, crsql_site_id()), cl, seq FROM crsql_changes WHERE db_version > ?", (since,))
@@ -895,6 +904,96 @@ def test_pko_resurrect():
895904
close(c2)
896905

897906

907+
# The case is:
908+
# Nodes A, B and C all have the same row
909+
# C deletes it
910+
# A receives the delete
911+
# B receives the delete
912+
# C re-inserts it
913+
# B receive the re-insertion and all cols for that re-insert
914+
# A receives nothing
915+
# C updates a few columns
916+
# A receives the column updates
917+
# B receives the column updates
918+
# A now receives the re-inesrtion and all colls for that re-insert
919+
# Everyone should be converged. In the user report,
920+
# A lost the values from the update.
921+
# https://discord.com/channels/989870439897653248/1145793546788544542/1146199296019009576
922+
def test_discord_report_corrosion():
923+
changes_q = "SELECT * FROM crsql_changes"
924+
925+
def make_schema():
926+
c = connect(":memory:")
927+
c.execute("CREATE TABLE foo (a primary key, b, c, d, e)")
928+
c.execute("SELECT crsql_as_crr('foo')")
929+
c.commit()
930+
return c
931+
932+
a = make_schema()
933+
b = make_schema()
934+
c = make_schema()
935+
936+
c.execute("INSERT INTO foo VALUES (1, 'b', 'c', 'd', 'e')")
937+
c.commit()
938+
sync_left_to_right_single_vrsn(c, a, 1)
939+
sync_left_to_right_single_vrsn(c, b, 1)
940+
941+
# all dbs in same state
942+
# c now does the delete
943+
c.execute("DELETE FROM foo WHERE a = 1")
944+
c.commit()
945+
946+
# that delete now goes to peers
947+
sync_left_to_right_single_vrsn(c, a, 2)
948+
sync_left_to_right_single_vrsn(c, b, 2)
949+
950+
# c now re-inserts
951+
c.execute("INSERT INTO foo VALUES (1, 'b1', 'c1', 'd1', 'e1')")
952+
c.commit()
953+
954+
# b gets the re-insertion
955+
sync_left_to_right_single_vrsn(c, b, 3)
956+
957+
# c makes updates
958+
c.execute("UPDATE foo SET b = 'b2', c = 'c2' WHERE a = 1")
959+
c.commit()
960+
961+
# those updates go to A and B
962+
sync_left_to_right_exact_version(c, a, 4)
963+
sync_left_to_right_exact_version(c, b, 4)
964+
965+
# make sure we have the expected change coming out of node C
966+
assert (c.execute(
967+
"SELECT cid, col_version, cl FROM crsql_changes WHERE db_version = 4").fetchall() ==
968+
[('b', 2, 3), ('c', 2, 3)])
969+
970+
# a received the delete followed by updates of cells `b` and `c`
971+
# so it's changes should only have:
972+
# 1. the sentinal with causal length 3
973+
# 2. cell b with col version 2 and causal length 3
974+
# 3. cell c with the same
975+
assert (a.execute("SELECT cid, col_version, cl FROM crsql_changes").fetchall(
976+
) == [('-1', 3, 3), ('b', 2, 3), ('c', 2, 3)])
977+
978+
# now that old re-insert goes to A
979+
sync_left_to_right_single_vrsn(c, a, 3)
980+
981+
# nodes have the same final tables
982+
q = "SELECT * FROM foo"
983+
assert (a.execute(q).fetchall() ==
984+
b.execute(q).fetchall())
985+
986+
# all nodes should have the same:
987+
# 1. column versions
988+
# 2. causal length
989+
# 3. value
990+
# db_versions could be different as changes are applied in different
991+
# orders on each node.
992+
q = "SELECT cid, col_version, cl, val FROM crsql_changes ORDER BY cid"
993+
assert (a.execute(q).fetchall() == b.execute(q).fetchall())
994+
assert (a.execute(q).fetchall() == c.execute(q).fetchall())
995+
996+
898997
def test_cl_does_not_move_forward_when_equal():
899998
None
900999

0 commit comments

Comments
 (0)