|
@@ -1440,23 +1440,28 @@ void test_validate_on_write_unrelated_update_not_rechecked() {
|
|
|
"the update itself still applied");
|
|
"the update itself still applied");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// v2.11.0 T13 round 2 (review finding) — rollback of an ALREADY-APPLIED
|
|
|
|
|
|
|
+// v2.11.0 T13 round 3 (review finding) — rollback of an ALREADY-APPLIED
|
|
|
// index mutation within the same write, not merely "the mutation never
|
|
// index mutation within the same write, not merely "the mutation never
|
|
|
// happened because validation ran first."
|
|
// happened because validation ran first."
|
|
|
//
|
|
//
|
|
|
-// The earlier "left no posting behind" assertions are structurally weak on
|
|
|
|
|
-// their own: validate_on_write's check runs before that RELATION's own
|
|
|
|
|
-// index sub-db is even opened, so on rejection the sub-db often never
|
|
|
|
|
-// exists and relation_index_child_count returns 0 through the nullopt path
|
|
|
|
|
-// regardless of whether LMDB actually rolled anything back. This test
|
|
|
|
|
-// forces a real rollback to matter: `executions` declares TWO relations.
|
|
|
|
|
-// The first (wf_rel, validateOnWrite=false) has its posting WRITTEN - a
|
|
|
|
|
-// real mdb_put against a real sub-db, inside the loop, before the second
|
|
|
|
|
-// relation is even considered. The second (owner_rel, validateOnWrite=true)
|
|
|
|
|
-// then rejects. Both relations share the one write transaction `put()`
|
|
|
|
|
-// opens, so the abort must undo wf_rel's already-applied mdb_put along with
|
|
|
|
|
-// everything else - there is no sub-db-never-existed shortcut available
|
|
|
|
|
-// here, because it demonstrably did exist and did get written to.
|
|
|
|
|
|
|
+// round 2's version of this test made `_relidx_wf_rel` itself only exist
|
|
|
|
|
+// inside the SAME aborted transaction (nothing had committed a wf_rel
|
|
|
|
|
+// posting beforehand), so relation_index_child_count("wf_rel", "wf-1")
|
|
|
|
|
+// still returned 0 through the "sub-db was never created"
|
|
|
|
|
+// (`if (!dbi_opt) return 0;`) shortcut regardless of whether the abort
|
|
|
|
|
+// rolled anything back - the exact case the comment claimed was
|
|
|
|
|
+// unavailable. Fixed by committing a SIBLING child (e0) first, in its own,
|
|
|
|
|
+// separate, successful write: `_relidx_wf_rel` is a real, already-existing
|
|
|
|
|
+// sub-db holding one committed posting (from e0) BEFORE the rejected write
|
|
|
|
|
+// (e1) runs. `executions` declares TWO relations: wf_rel
|
|
|
|
|
+// (validateOnWrite=false) and owner_rel (validateOnWrite=true). e1's write
|
|
|
|
|
+// applies wf_rel's mdb_put for real - into that already-existing sub-db -
|
|
|
|
|
+// before owner_rel's check runs and rejects. The assertion that matters is
|
|
|
|
|
+// that the count STAYS AT 1 (e0's), not 2: reading 2 would mean e1's
|
|
|
|
|
+// already-applied wf_rel mutation survived the abort. Because the sub-db
|
|
|
|
|
+// demonstrably existed beforehand (asserted directly, see the sanity check
|
|
|
|
|
+// below), the nullopt shortcut is not in play here, and the assertion
|
|
|
|
|
+// actually discriminates a rollback from a no-op.
|
|
|
void test_validate_on_write_rolls_back_an_already_applied_sibling_relation() {
|
|
void test_validate_on_write_rolls_back_an_already_applied_sibling_relation() {
|
|
|
TmpEnv t("validate-rollback-sibling");
|
|
TmpEnv t("validate-rollback-sibling");
|
|
|
LmdbDocumentStore store(t.env);
|
|
LmdbDocumentStore store(t.env);
|
|
@@ -1468,7 +1473,20 @@ void test_validate_on_write_rolls_back_an_already_applied_sibling_relation() {
|
|
|
Document w; w.id = "wf-1"; w.collection = "workflows";
|
|
Document w; w.id = "wf-1"; w.collection = "workflows";
|
|
|
w.set_data({{"name", "real workflow"}});
|
|
w.set_data({{"name", "real workflow"}});
|
|
|
store.put("workflows", "wf-1", w);
|
|
store.put("workflows", "wf-1", w);
|
|
|
- // Deliberately no "users/u-ghost" - owner_rel's parent never exists.
|
|
|
|
|
|
|
+
|
|
|
|
|
+ Document uReal; uReal.id = "u-real"; uReal.collection = "users";
|
|
|
|
|
+ uReal.set_data({{"name", "a real user"}});
|
|
|
|
|
+ store.put("users", "u-real", uReal);
|
|
|
|
|
+ // Deliberately no "users/u-ghost" - owner_rel's parent for e1 never exists.
|
|
|
|
|
+
|
|
|
|
|
+ // Commit a sibling child FIRST, in its own successful write, so
|
|
|
|
|
+ // `_relidx_wf_rel` is a real, already-existing sub-db with one committed
|
|
|
|
|
+ // posting before the rejected write below ever runs.
|
|
|
|
|
+ Document e0; e0.id = "e0"; e0.collection = "executions";
|
|
|
|
|
+ e0.set_data({{"workflowId", "wf-1"}, {"ownerId", "u-real"}});
|
|
|
|
|
+ store.put("executions", "e0", e0);
|
|
|
|
|
+ check(store.relation_index_child_count("wf_rel", "wf-1") == 1,
|
|
|
|
|
+ "sanity: wf_rel's sub-db already exists and holds e0's committed posting");
|
|
|
|
|
|
|
|
Document e; e.id = "e1"; e.collection = "executions";
|
|
Document e; e.id = "e1"; e.collection = "executions";
|
|
|
e.set_data({{"workflowId", "wf-1"}, {"ownerId", "u-ghost"}});
|
|
e.set_data({{"workflowId", "wf-1"}, {"ownerId", "u-ghost"}});
|
|
@@ -1482,16 +1500,16 @@ void test_validate_on_write_rolls_back_an_already_applied_sibling_relation() {
|
|
|
check(!store.get("executions", "e1").has_value(),
|
|
check(!store.get("executions", "e1").has_value(),
|
|
|
"the document itself was rolled back");
|
|
"the document itself was rolled back");
|
|
|
check(store.relation_index_child_count("owner_rel", "u-ghost") == 0,
|
|
check(store.relation_index_child_count("owner_rel", "u-ghost") == 0,
|
|
|
- "owner_rel (the relation that rejected) has no posting");
|
|
|
|
|
- check(store.relation_index_child_count("wf_rel", "wf-1") == 0,
|
|
|
|
|
- "wf_rel (the EARLIER, already-applied sibling relation) was rolled "
|
|
|
|
|
- "back too - LMDB's transaction abort undid a real mdb_put, not "
|
|
|
|
|
- "just 'the sub-db never got created'");
|
|
|
|
|
|
|
+ "owner_rel (the relation that rejected) has no posting for the ghost id");
|
|
|
|
|
+ check(store.relation_index_child_count("wf_rel", "wf-1") == 1,
|
|
|
|
|
+ "wf_rel's count STAYS AT 1 (e0's) rather than becoming 2 - e1's "
|
|
|
|
|
+ "already-applied mdb_put into this REAL, already-existing sub-db "
|
|
|
|
|
+ "was rolled back by the same transaction abort that rejected "
|
|
|
|
|
+ "owner_rel. A count of 2 here would mean the sibling relation's "
|
|
|
|
|
+ "mutation survived the abort.");
|
|
|
|
|
|
|
|
// Once owner_rel's parent exists, the identical write succeeds and BOTH
|
|
// Once owner_rel's parent exists, the identical write succeeds and BOTH
|
|
|
- // relations end up with their postings - confirming the rollback above
|
|
|
|
|
- // was real and not a side effect of some other bug losing wf_rel's
|
|
|
|
|
- // posting permanently.
|
|
|
|
|
|
|
+ // relations end up with their postings - e0's plus e1's.
|
|
|
Document u; u.id = "u-ghost"; u.collection = "users";
|
|
Document u; u.id = "u-ghost"; u.collection = "users";
|
|
|
u.set_data({{"name", "real user, now created"}});
|
|
u.set_data({{"name", "real user, now created"}});
|
|
|
store.put("users", "u-ghost", u);
|
|
store.put("users", "u-ghost", u);
|
|
@@ -1502,8 +1520,9 @@ void test_validate_on_write_rolls_back_an_already_applied_sibling_relation() {
|
|
|
threw = true;
|
|
threw = true;
|
|
|
}
|
|
}
|
|
|
check(!threw, "once owner_rel's parent exists too, the write succeeds");
|
|
check(!threw, "once owner_rel's parent exists too, the write succeeds");
|
|
|
- check(store.relation_index_child_count("wf_rel", "wf-1") == 1, "wf_rel posted");
|
|
|
|
|
- check(store.relation_index_child_count("owner_rel", "u-ghost") == 1, "owner_rel posted");
|
|
|
|
|
|
|
+ check(store.relation_index_child_count("wf_rel", "wf-1") == 2,
|
|
|
|
|
+ "wf_rel now has BOTH e0's (pre-existing) and e1's (just landed) postings");
|
|
|
|
|
+ check(store.relation_index_child_count("owner_rel", "u-ghost") == 1, "owner_rel posted for e1");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// v2.11.0 T13 round 2 (review finding 1) — relationsEnforced is the
|
|
// v2.11.0 T13 round 2 (review finding 1) — relationsEnforced is the
|