use super::*;
#[test]
fn a_columnar_set_journals_one_pre_image_per_changed_node() {
use crate::graph::storage::undo::{
journal_columnar_cells, journal_node_pre_images, reset_journal_columnar_cells,
reset_journal_node_pre_images,
};
let mut graph = wide_columnar();
reset_journal_node_pre_images();
reset_journal_columnar_cells();
run(&mut graph, "MATCH (i:Item {id: 7}) SET i.priority = 3");
let captured = journal_node_pre_images();
let cells = journal_columnar_cells();
assert!(
captured <= 2,
"a one-row columnar SET captured {captured} node pre-images across \
{WIDE_ITEMS} nodes of the type; it must be O(nodes changed), not \
O(nodes of the type) — the handle-refresh sweep is being journalled"
);
assert_eq!(
cells, 1,
"a one-row, one-property columnar SET must journal exactly one cell \
pre-image across {WIDE_ITEMS} nodes of the type; {cells} means the \
capture is sized by something other than the change"
);
}
#[test]
fn a_columnar_set_journals_one_cell_per_changed_property() {
use crate::graph::storage::undo::{journal_columnar_cells, reset_journal_columnar_cells};
let mut graph = wide_columnar();
reset_journal_columnar_cells();
run(
&mut graph,
"MATCH (i:Item {id: 7}) SET i.qty = 1, i.priority = 3, i.rank = 5",
);
assert_eq!(
journal_columnar_cells(),
3,
"three changed cells must journal three pre-images"
);
reset_journal_columnar_cells();
run(&mut graph, "MATCH (i:Item) WHERE i.id < 2 SET i.qty = 9");
assert_eq!(
journal_columnar_cells(),
2,
"two changed rows must journal two pre-images, not {WIDE_ITEMS}"
);
}
#[test]
fn a_mapped_columnar_set_journals_one_pre_image_per_changed_node() {
use crate::graph::storage::undo::{
journal_columnar_cells, journal_node_pre_images, reset_journal_columnar_cells,
reset_journal_node_pre_images,
};
let mut graph = wide_columnar_mapped();
reset_journal_node_pre_images();
reset_journal_columnar_cells();
run(&mut graph, "MATCH (i:Item {id: 7}) SET i.priority = 3");
let captured = journal_node_pre_images();
let cells = journal_columnar_cells();
assert_eq!(
cells, 1,
"a one-row columnar SET on a mapped graph must journal exactly one \
cell pre-image, not {cells}"
);
assert!(
captured <= 2,
"a one-row columnar SET on a mapped graph captured {captured} node \
pre-images across {WIDE_ITEMS} nodes of the type; the mapped \
handle-refresh sweep is being journalled"
);
}
#[test]
fn an_unmapped_set_journals_one_pre_image_per_changed_node() {
use crate::graph::storage::undo::{journal_node_pre_images, reset_journal_node_pre_images};
let mut graph = wide_columnar();
reset_journal_node_pre_images();
run(&mut graph, "MATCH (i:Item {id: 7}) SET i.priority = 3");
let captured = journal_node_pre_images();
assert!(
captured <= 2,
"a one-row SET on an unmapped graph captured {captured} node \
pre-images across {WIDE_ITEMS} nodes"
);
}
#[test]
fn two_columnar_writes_in_one_statement_both_land() {
let mut graph = wide_columnar();
let idx = graph
.graph
.node_indices()
.find(|i| {
graph
.graph
.node_view(*i)
.and_then(|n| n.get_property_value("qty"))
.map(|v| v == crate::datatypes::Value::Int64(1))
.unwrap_or(false)
})
.expect("fixture seeds qty = node index");
let allocation_before = Arc::as_ptr(graph.column_store("Item").expect("master"));
run(
&mut graph,
"MATCH (n:Item {id: 1}) SET n.qty = 111 SET n.qty = 222",
);
assert!(
std::ptr::eq(
allocation_before,
Arc::as_ptr(graph.column_store("Item").expect("master"))
),
"neither write may fork the master"
);
let node = graph.graph.node_view(idx).expect("node still present");
assert_eq!(
node.get_property_value("qty"),
Some(crate::datatypes::Value::Int64(222)),
"both writes must be visible; reading 1 means the second write landed \
somewhere the read route does not resolve"
);
let master = graph.column_store("Item").expect("master");
assert_eq!(
Arc::strong_count(master),
1,
"nothing but the backend may hold the master, or every write pays a \
whole-store copy"
);
}
#[test]
fn a_multi_node_create_journals_one_append_pre_image_per_type() {
use crate::graph::storage::undo::{journal_columnar_appends, reset_journal_columnar_appends};
let mut graph = wide_columnar();
reset_journal_columnar_appends();
run(
&mut graph,
"CREATE (:Item {id: 900, name: 'x', qty: 1}), \
(:Item {id: 901, name: 'y', qty: 2}), \
(:Item {id: 902, name: 'z', qty: 3})",
);
assert_eq!(
journal_columnar_appends(),
1,
"three rows appended to one type must journal one append pre-image"
);
reset_journal_columnar_appends();
run(
&mut graph,
"CREATE (:Item {id: 910, name: 'p', qty: 1}), \
(:Item {id: 911, name: 'q', qty: 2}), \
(:Widget {id: 1, name: 'w'}), (:Widget {id: 2, name: 'v'})",
);
assert_eq!(
journal_columnar_appends(),
2,
"two types must journal one append pre-image each — a per-statement \
dedup would drop the second type's undo"
);
}
#[test]
fn a_failed_multi_node_create_of_a_new_type_rolls_back_to_nothing() {
let mut graph = wide_columnar();
assert!(
graph.column_store("Batch").is_none(),
"precondition: the type must not exist yet, or this is vacuous"
);
assert_rolls_back(
&mut graph,
"CREATE (:Batch {id: 1, name: 'a', qty: 1}), \
(:Batch {id: 2, name: 'b', qty: 2}), \
(:Batch {id: 3, name: 'c', qty: 3}) \
WITH 1 AS ignored MATCH (m:Item {id: 2}) \
SET m.qty = duration({months: 2147483648})",
None,
);
assert!(
graph.column_store("Batch").is_none(),
"a rolled-back CREATE of a new type left its master store behind"
);
assert_eq!(
graph.type_indices.get("Batch").map(|b| b.len()),
None,
"and must leave no type bucket"
);
}
#[test]
fn a_failed_multi_node_create_truncates_to_the_pre_statement_row_count() {
let mut graph = wide_columnar();
let before = graph
.column_store("Item")
.expect("fixture installs the master")
.row_count();
assert_rolls_back(
&mut graph,
"CREATE (:Item {id: 800, name: 'a', qty: 1}), \
(:Item {id: 801, name: 'b', qty: 2}), \
(:Item {id: 802, name: 'c', qty: 3}) \
WITH 1 AS ignored MATCH (m:Item {id: 2}) \
SET m.qty = duration({months: 2147483648})",
None,
);
assert_eq!(
graph
.column_store("Item")
.expect("master survives")
.row_count(),
before,
"the rolled-back appends must truncate to the pre-statement row count"
);
}
#[test]
fn no_node_holds_a_column_store_handle() {
let graph = wide_columnar();
let master = graph
.column_store("Item")
.expect("the fixture installs a master store for Item");
assert_eq!(
Arc::strong_count(master),
1,
"the backend must be the only owner of the master; a second handle \
means something re-introduced a replica, and every columnar write \
would silently go back to copying the whole store"
);
let columnar = graph
.graph
.node_indices()
.filter(|idx| {
matches!(
graph.graph.node_weight(*idx).map(|n| &n.properties),
Some(PropertyStorage::Columnar(_))
)
})
.count();
assert_eq!(
columnar, WIDE_ITEMS,
"every node of the type must still be columnar, or the refcount above \
is 1 because the fixture stopped being saved"
);
}
#[test]
fn the_master_is_uniquely_owned_between_statements() {
let mut graph = wide_columnar();
assert_eq!(
Arc::strong_count(graph.column_store("Item").expect("master")),
1,
"precondition: uniquely owned before any statement"
);
let allocation_before = Arc::as_ptr(graph.column_store("Item").expect("master"));
run(&mut graph, "MATCH (n:Item {id: 1}) SET n.qty = 111");
assert_eq!(
Arc::strong_count(graph.column_store("Item").expect("master")),
1,
"a committed statement must leave nothing else holding the master"
);
assert!(
std::ptr::eq(
allocation_before,
Arc::as_ptr(graph.column_store("Item").expect("master"))
),
"the statement replaced the master's allocation, so `Arc::make_mut` \
forked mid-statement: something held a second handle while the write \
ran and the write copied {WIDE_ITEMS} rows to change one cell"
);
let allocation_before = Arc::as_ptr(graph.column_store("Item").expect("master"));
expect_failure(
&mut graph,
"MATCH (n:Item {id: 2}) SET n.qty = 222 \
WITH n MATCH (m:Item {id: 3}) SET m.qty = duration({months: 2147483648})",
None,
);
assert!(
std::ptr::eq(
allocation_before,
Arc::as_ptr(graph.column_store("Item").expect("master"))
),
"a rolled-back statement must restore cells into the live store, not \
swap a pre-statement copy back in"
);
assert_eq!(
Arc::strong_count(graph.column_store("Item").expect("master")),
1,
"and must leave the master uniquely owned"
);
assert_eq!(
graph
.graph
.node_view(
graph
.graph
.node_indices()
.find(|i| graph.graph.get_node_id(*i)
== Some(crate::datatypes::Value::Int64(1)))
.expect("node 1")
)
.and_then(|n| n.get_property_value("qty")),
Some(crate::datatypes::Value::Int64(111)),
"and the write must actually be visible"
);
}