use cypherlite_core::{DatabaseConfig, SyncMode};
use cypherlite_query::api::CypherLite;
use tempfile::tempdir;
fn test_db(dir: &std::path::Path) -> CypherLite {
let config = DatabaseConfig {
path: dir.join("test.cyl"),
wal_sync_mode: SyncMode::Normal,
..Default::default()
};
CypherLite::open(config).expect("open")
}
#[test]
fn inline_filter_single_property() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (:Person {name: 'Alice', age: 30})")
.expect("create alice");
db.execute("CREATE (:Person {name: 'Bob', age: 25})")
.expect("create bob");
db.execute("CREATE (:Person {name: 'Charlie', age: 35})")
.expect("create charlie");
let result = db
.execute("MATCH (n:Person {name: 'Alice'}) RETURN n.name, n.age")
.expect("query");
assert_eq!(result.rows.len(), 1, "should return only Alice");
assert_eq!(
result.rows[0].get_as::<String>("n.name"),
Some("Alice".to_string())
);
}
#[test]
fn inline_filter_multiple_properties() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (:Person {name: 'Alice', age: 30})")
.expect("create alice");
db.execute("CREATE (:Person {name: 'Alice', age: 25})")
.expect("create alice-younger");
db.execute("CREATE (:Person {name: 'Bob', age: 30})")
.expect("create bob");
let result = db
.execute("MATCH (n:Person {name: 'Alice', age: 30}) RETURN n.name, n.age")
.expect("query");
assert_eq!(result.rows.len(), 1, "should return only Alice with age 30");
assert_eq!(
result.rows[0].get_as::<String>("n.name"),
Some("Alice".to_string())
);
assert_eq!(result.rows[0].get_as::<i64>("n.age"), Some(30));
}
#[test]
fn inline_filter_null_property() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (:Person {name: 'Alice', email: 'alice@example.com'})")
.expect("create alice");
db.execute("CREATE (:Person {name: 'Bob'})")
.expect("create bob");
let result = db
.execute("MATCH (n:Person {email: null}) RETURN n.name")
.expect("query");
assert!(
result.rows.is_empty(),
"null = null is null in Cypher, so no node matches {{email: null}}"
);
}
#[test]
fn inline_filter_combined_with_where() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (:Person {name: 'Alice', age: 30})")
.expect("create alice");
db.execute("CREATE (:Person {name: 'Alice', age: 20})")
.expect("create alice-young");
db.execute("CREATE (:Person {name: 'Bob', age: 40})")
.expect("create bob");
let result = db
.execute("MATCH (n:Person {name: 'Alice'}) WHERE n.age > 25 RETURN n.name, n.age")
.expect("query");
assert_eq!(
result.rows.len(),
1,
"should return only Alice with age > 25"
);
assert_eq!(
result.rows[0].get_as::<String>("n.name"),
Some("Alice".to_string())
);
assert_eq!(result.rows[0].get_as::<i64>("n.age"), Some(30));
}
#[test]
fn inline_filter_empty_map() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (:Person {name: 'Alice'})")
.expect("create");
db.execute("CREATE (:Person {name: 'Bob'})")
.expect("create");
let result = db
.execute("MATCH (n:Person {}) RETURN n.name")
.expect("query");
assert_eq!(
result.rows.len(),
2,
"empty map should return all Person nodes"
);
}
#[test]
fn inline_filter_no_match() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (:Person {name: 'Alice'})")
.expect("create");
db.execute("CREATE (:Person {name: 'Bob'})")
.expect("create");
let result = db
.execute("MATCH (n:Person {name: 'NonExistent'}) RETURN n.name")
.expect("query");
assert!(
result.rows.is_empty(),
"non-existent property value should yield empty result"
);
}
#[test]
fn inline_filter_rel_single_property() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (a:Src1 {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:Dst1 {name: 'Bob'})")
.expect("create chain1");
db.execute(
"CREATE (a:Src2 {name: 'Alice'})-[:KNOWS {since: 2015}]->(b:Dst2 {name: 'Charlie'})",
)
.expect("create chain2");
let all = db
.execute("MATCH (a)-[r:KNOWS]->(b) RETURN r.since")
.expect("all");
assert_eq!(all.rows.len(), 2, "unfiltered should return 2 edges");
let result = db
.execute("MATCH (a)-[r:KNOWS {since: 2020}]->(b) RETURN r.since, b.name")
.expect("query");
assert_eq!(
result.rows.len(),
1,
"should return only the edge with since=2020"
);
assert_eq!(result.rows[0].get_as::<i64>("r.since"), Some(2020));
assert_eq!(
result.rows[0].get_as::<String>("b.name"),
Some("Bob".to_string())
);
}
#[test]
fn inline_filter_rel_multiple_properties() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute(
"CREATE (a:M1 {name: 'Alice'})-[:KNOWS {since: 2020, strength: 5}]->(b:M2 {name: 'Bob'})",
)
.expect("create chain1");
db.execute(
"CREATE (a:M3 {name: 'Alice'})-[:KNOWS {since: 2020, strength: 3}]->(b:M4 {name: 'Charlie'})",
)
.expect("create chain2");
let result = db
.execute("MATCH (a)-[r:KNOWS {since: 2020, strength: 5}]->(b) RETURN r.since, r.strength")
.expect("query");
assert_eq!(
result.rows.len(),
1,
"should match only the edge with since=2020 AND strength=5"
);
assert_eq!(result.rows[0].get_as::<i64>("r.since"), Some(2020));
assert_eq!(result.rows[0].get_as::<i64>("r.strength"), Some(5));
}
#[test]
fn inline_filter_target_node_property() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (x:Src {name: 'Alice'})-[:KNOWS]->(y:Dst {name: 'Bob'})")
.expect("create chain1");
db.execute("CREATE (x:Src {name: 'Alice'})-[:KNOWS]->(y:Dst {name: 'Charlie'})")
.expect("create chain2");
let all = db
.execute("MATCH (a:Src)-[:KNOWS]->(b:Dst) RETURN b.name")
.expect("all");
assert_eq!(all.rows.len(), 2, "unfiltered should return 2 targets");
let result = db
.execute("MATCH (a:Src)-[:KNOWS]->(b:Dst {name: 'Bob'}) RETURN b.name")
.expect("query");
assert_eq!(result.rows.len(), 1, "should return only Bob as target");
assert_eq!(
result.rows[0].get_as::<String>("b.name"),
Some("Bob".to_string())
);
}
#[test]
fn inline_filter_source_and_rel_combined() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (a:S1 {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:T1 {name: 'Bob'})")
.expect("create chain1");
db.execute("CREATE (a:S1 {name: 'Dave'})-[:KNOWS {since: 2020}]->(b:T2 {name: 'Bob'})")
.expect("create chain2");
let result = db
.execute("MATCH (a:S1 {name: 'Alice'})-[r:KNOWS {since: 2020}]->(b) RETURN r.since, b.name")
.expect("query");
assert_eq!(
result.rows.len(),
1,
"should return only path from Alice with since=2020"
);
assert_eq!(result.rows[0].get_as::<i64>("r.since"), Some(2020));
assert_eq!(
result.rows[0].get_as::<String>("b.name"),
Some("Bob".to_string())
);
}
#[test]
fn inline_filter_anonymous_rel_with_property() {
let dir = tempdir().expect("tempdir");
let mut db = test_db(dir.path());
db.execute("CREATE (a:A1 {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:B1 {name: 'Bob'})")
.expect("create chain1");
db.execute("CREATE (a:A2 {name: 'Alice'})-[:KNOWS {since: 2015}]->(b:B2 {name: 'Charlie'})")
.expect("create chain2");
let result = db
.execute("MATCH (a)-[:KNOWS {since: 2020}]->(b) RETURN b.name")
.expect("query");
assert_eq!(
result.rows.len(),
1,
"anonymous rel with inline props should still filter"
);
assert_eq!(
result.rows[0].get_as::<String>("b.name"),
Some("Bob".to_string())
);
}