use super::*;
fn item_rows(graph: &DirGraph) -> (u32, u32) {
let store = graph.column_store("Item").expect("master store");
(store.row_count(), store.live_count())
}
#[test]
fn a_rolled_back_create_restores_the_row_count_and_the_next_row_id() {
let mut graph = seeded();
let (rows_before, live_before) = item_rows(&graph);
run(&mut graph, "CREATE (:Item {id: 90, name: 'probe', qty: 1})");
assert_eq!(
item_rows(&graph),
(rows_before + 1, live_before + 1),
"a CREATE must append one master row, or the rollback assertions below \
are vacuous"
);
run(&mut graph, "MATCH (n:Item {id: 90}) DELETE n");
assert_eq!(
item_rows(&graph),
(rows_before + 1, live_before),
"a DELETE must tombstone the row it removed the node for"
);
let before = fingerprint(&mut graph);
let (rows, live) = item_rows(&graph);
expect_failure(
&mut graph,
"CREATE (:Item {id: 100, name: 'a', qty: 1}), \
(:Item {id: 101, bad: duration({months: 2147483648})})",
None,
);
assert_eq!(
item_rows(&graph),
(rows, live),
"a rolled-back CREATE must truncate the rows it appended"
);
assert_eq!(fingerprint(&mut graph), before);
run(&mut graph, "CREATE (:Item {id: 102, name: 'b', qty: 2})");
let idx = graph
.graph
.node_indices()
.find(|i| graph.graph.get_node_id(*i) == Some(Value::Int64(102)))
.expect("the new node");
let row = match graph.graph.node_weight(idx).map(|n| &n.properties) {
Some(PropertyStorage::Columnar(row)) => row.row_id(),
other => panic!("expected a columnar row, got {other:?}"),
};
assert_eq!(row, rows, "the re-created node must reuse the vacated row");
assert_eq!(
item_prop(&graph, 102, "name"),
Some(Value::String("b".into())),
"and read its own values back out of it"
);
}
#[test]
fn a_rolled_back_create_of_a_new_type_leaves_no_store() {
let mut graph = seeded();
let before = fingerprint(&mut graph);
assert!(
graph.column_store("Fresh").is_none(),
"precondition: the type must not exist yet"
);
expect_failure(
&mut graph,
"CREATE (:Fresh {id: 1, name: 'a'}), \
(:Fresh {id: 2, bad: duration({months: 2147483648})})",
None,
);
assert!(
graph.column_store("Fresh").is_none(),
"a rolled-back CREATE must not leave an empty store for a type that \
never existed"
);
assert_eq!(fingerprint(&mut graph), before);
}
#[test]
fn a_rolled_back_delete_untombstones_the_row() {
let mut graph = seeded();
let before = fingerprint(&mut graph);
let (rows, live) = item_rows(&graph);
expect_failure(
&mut graph,
"MATCH (n:Item {id: 1}) DELETE n WITH n MATCH (m:Item {id: 2}) \
SET m.qty = duration({months: 2147483648})",
None,
);
assert_eq!(
item_rows(&graph),
(rows, live),
"a rolled-back DELETE must restore the row's liveness"
);
assert_eq!(
item_prop(&graph, 1, "qty"),
Some(Value::Int64(10)),
"and the restored node must read its own row"
);
assert_eq!(fingerprint(&mut graph), before);
}
#[test]
fn a_failed_statement_that_creates_sets_and_deletes_rolls_all_three_back() {
let mut graph = seeded();
let before = fingerprint(&mut graph);
let (rows, live) = item_rows(&graph);
expect_failure(
&mut graph,
"CREATE (x:Item {id: 200, name: 'new', qty: 1}) \
WITH x MATCH (a:Item {id: 1}), (b:Item {id: 3}) \
SET a.qty = 999, x.qty = 5 \
DELETE b \
WITH a MATCH (m:Item {id: 2}) \
SET m.qty = duration({months: 2147483648})",
None,
);
assert_eq!(item_rows(&graph), (rows, live));
assert_eq!(item_prop(&graph, 1, "qty"), Some(Value::Int64(10)));
assert_eq!(item_prop(&graph, 3, "qty"), Some(Value::Int64(30)));
assert_eq!(fingerprint(&mut graph), before);
}
#[test]
fn the_three_way_statement_rolls_back_on_every_fixture() {
for mut graph in [
seeded(),
seeded_columnar(),
seeded_indexed(),
seeded_mapped(),
] {
let before = fingerprint(&mut graph);
expect_failure(
&mut graph,
"CREATE (x:Item {id: 200, name: 'new', qty: 1}) \
WITH x MATCH (a:Item {id: 1}), (b:Item {id: 3}) \
SET a.qty = 999 \
DELETE b \
WITH a MATCH (m:Item {id: 2}) \
SET m.qty = duration({months: 2147483648})",
None,
);
assert_eq!(fingerprint(&mut graph), before);
}
}
#[test]
fn a_title_write_goes_through_the_master_and_rolls_back() {
let mut graph = seeded();
let before = fingerprint(&mut graph);
run(&mut graph, "MATCH (n:Item {id: 1}) SET n.title = 'moved'");
let idx = graph
.graph
.node_indices()
.find(|i| graph.graph.get_node_id(*i) == Some(Value::Int64(1)))
.expect("the node");
let row = match graph.graph.node_weight(idx).map(|n| &n.properties) {
Some(PropertyStorage::Columnar(row)) => row.row_id(),
other => panic!("expected a columnar row, got {other:?}"),
};
assert_eq!(
graph.column_store("Item").expect("master").get_title(row),
Some(Value::String("moved".into())),
"the title must be written through the master's reserved column"
);
assert!(
matches!(
graph.graph.node_weight(idx).map(|n| &n.title),
Some(Value::Null)
),
"and the inline field must stay on its sentinel — a second copy is what \
forced a rebuild at save"
);
let after_write = fingerprint(&mut graph);
assert_ne!(after_write, before, "the write must be observable");
expect_failure(
&mut graph,
&format!("MATCH (n:Item {{id: 1}}) SET n.title = 'doomed' {FAILS_AFTER_A_COLUMNAR_WRITE}"),
None,
);
assert_eq!(
fingerprint(&mut graph),
after_write,
"a rolled-back title write must restore the prior title"
);
}