use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
#[test]
fn test_merge_on_match_set_rollback() {
let db = GrafeoDB::new_in_memory();
let mut session = db.session();
session
.execute("INSERT (:Person {name: 'Alix', status: 'active'})")
.unwrap();
session.begin_transaction().unwrap();
session
.execute("MERGE (p:Person {name: 'Alix'}) ON MATCH SET p.status = 'inactive'")
.unwrap();
let result = session
.execute("MATCH (p:Person {name: 'Alix'}) RETURN p.status")
.unwrap();
assert_eq!(result.rows()[0][0], Value::String("inactive".into()));
session.rollback().unwrap();
let result = session
.execute("MATCH (p:Person {name: 'Alix'}) RETURN p.status")
.unwrap();
assert_eq!(
result.rows()[0][0],
Value::String("active".into()),
"status should be restored to 'active' after rollback"
);
}
#[test]
fn test_merge_on_match_set_new_property_rollback() {
let db = GrafeoDB::new_in_memory();
let mut session = db.session();
session.execute("INSERT (:Person {name: 'Gus'})").unwrap();
session.begin_transaction().unwrap();
session
.execute("MERGE (p:Person {name: 'Gus'}) ON MATCH SET p.updated = true")
.unwrap();
session.rollback().unwrap();
let result = session
.execute("MATCH (p:Person {name: 'Gus'}) RETURN p.updated")
.unwrap();
assert_eq!(
result.rows()[0][0],
Value::Null,
"'updated' property should not exist after rollback"
);
}
#[test]
fn test_merge_create_rolled_back_does_not_orphan_node() {
let db = GrafeoDB::new_in_memory();
let mut session = db.session();
session.begin_transaction().unwrap();
session.execute("MERGE (n:OrphanCheck {val: 1})").unwrap();
session.rollback().unwrap();
let result = session
.execute("MATCH (n:OrphanCheck) RETURN count(n) AS cnt")
.unwrap();
assert_eq!(
result.rows()[0][0],
Value::Int64(0),
"MERGE-created node must be undone by rollback (no orphan)"
);
}
#[test]
fn test_merge_relationship_create_rolled_back_does_not_orphan_edge() {
let db = GrafeoDB::new_in_memory();
let mut session = db.session();
session.execute("INSERT (:N {name: 'Alix'})").unwrap();
session.execute("INSERT (:N {name: 'Gus'})").unwrap();
session.begin_transaction().unwrap();
session
.execute(
"MATCH (a:N {name: 'Alix'}), (b:N {name: 'Gus'}) \
MERGE (a)-[:KNOWS]->(b)",
)
.unwrap();
session.rollback().unwrap();
let result = session
.execute("MATCH ()-[k:KNOWS]->() RETURN count(k) AS cnt")
.unwrap();
assert_eq!(
result.rows()[0][0],
Value::Int64(0),
"MERGE-created edge must be undone by rollback (no orphan)"
);
}
#[test]
fn test_merge_two_phase_on_create_failure_rolls_back_node() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
session
.execute("CREATE NODE TYPE Article (val INT64, slug STRING)")
.unwrap();
session
.execute_cypher("CREATE CONSTRAINT unique_slug FOR (a:Article) REQUIRE a.slug IS UNIQUE")
.unwrap();
session
.execute("INSERT (:Article {val: 0, slug: 'taken'})")
.unwrap();
let err = session
.execute(
"MERGE (a:Article {val: 1}) \
ON CREATE SET a.slug = coalesce(a.slug, 'taken')",
)
.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("UNIQUE") || msg.contains("unique") || msg.contains("constraint"),
"expected uniqueness violation error, got: {msg}"
);
let result = session
.execute("MATCH (a:Article) RETURN count(a) AS cnt")
.unwrap();
assert_eq!(
result.rows()[0][0],
Value::Int64(1),
"phase-one node must not survive a phase-two ON CREATE failure"
);
}
#[test]
fn test_merge_on_match_committed_stays() {
let db = GrafeoDB::new_in_memory();
let mut session = db.session();
session
.execute("INSERT (:Person {name: 'Vincent', score: 0})")
.unwrap();
session.begin_transaction().unwrap();
session
.execute("MERGE (p:Person {name: 'Vincent'}) ON MATCH SET p.score = 100")
.unwrap();
session.commit().unwrap();
let result = session
.execute("MATCH (p:Person {name: 'Vincent'}) RETURN p.score")
.unwrap();
assert_eq!(
result.rows()[0][0],
Value::Int64(100),
"score should retain the committed value"
);
}