#![cfg(not(target_arch = "wasm32"))]
use minigraf::{Minigraf, QueryResult, Value};
fn count_results(r: QueryResult) -> usize {
match r {
QueryResult::QueryResults { results, .. } => results.len(),
_ => 0,
}
}
fn query_strings(db: &Minigraf, q: &str) -> Vec<String> {
match db.execute(q).unwrap() {
QueryResult::QueryResults { results, .. } => results
.into_iter()
.flatten()
.filter_map(|v| match v {
Value::String(s) => Some(s),
_ => None,
})
.collect(),
_ => vec![],
}
}
#[test]
fn xtdb_basic_find_by_attribute_value() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:pablo :profession "painter"]
[:salvador :profession "painter"]
[:kafka :profession "writer"]
])"#,
)
.unwrap();
let painters = count_results(
db.execute(r#"(query [:find ?e :where [?e :profession "painter"]])"#)
.unwrap(),
);
assert_eq!(painters, 2, "should find 2 painters");
}
#[test]
fn xtdb_multi_attribute_join() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:e1 :role "admin"] [:e1 :active true]
[:e2 :role "user"] [:e2 :active true]
[:e3 :role "admin"] [:e3 :active false]
])"#,
)
.unwrap();
let active_admins = count_results(
db.execute(r#"(query [:find ?e :where [?e :role "admin"] [?e :active true]])"#)
.unwrap(),
);
assert_eq!(active_admins, 1, "only e1 is an active admin");
}
#[test]
fn xtdb_entity_reference_join() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:alice :dept :dept-eng]
[:bob :dept :dept-eng]
[:carol :dept :dept-hr]
])"#,
)
.unwrap();
let eng_employees = count_results(
db.execute(r#"(query [:find ?emp :where [?emp :dept :dept-eng]])"#)
.unwrap(),
);
assert_eq!(eng_employees, 2, "alice and bob are in engineering");
}
#[test]
fn xtdb_retraction_removes_specific_fact() {
let db = Minigraf::in_memory().unwrap();
db.execute(r#"(transact [[:alice :name "Alice"] [:alice :role "admin"]])"#)
.unwrap();
db.execute(r#"(retract [[:alice :role "admin"]])"#).unwrap();
let roles = count_results(
db.execute(r#"(query [:find ?r :where [?e :role ?r]])"#)
.unwrap(),
);
assert_eq!(roles, 0, "role should be retracted");
let names = count_results(
db.execute(r#"(query [:find ?n :where [?e :name ?n]])"#)
.unwrap(),
);
assert_eq!(names, 1, "name should survive retraction of role");
}
#[test]
fn xtdb_retracted_fact_not_visible() {
let db = Minigraf::in_memory().unwrap();
db.execute(r#"(transact [[:item :status "active"]])"#)
.unwrap();
db.execute(r#"(retract [[:item :status "active"]])"#)
.unwrap();
let n = count_results(
db.execute(r#"(query [:find ?s :where [?item :status ?s]])"#)
.unwrap(),
);
assert_eq!(n, 0, "retracted fact must not be visible");
}
#[test]
fn xtdb_as_of_returns_past_state() {
let db = Minigraf::in_memory().unwrap();
db.execute(r#"(transact [[:alice :role "user"]])"#).unwrap();
db.execute(r#"(retract [[:alice :role "user"]])"#).unwrap();
db.execute(r#"(transact [[:alice :role "admin"]])"#)
.unwrap();
let current = query_strings(&db, r#"(query [:find ?r :where [?e :role ?r]])"#);
assert!(
current.contains(&"admin".to_string()),
"current role should be admin"
);
let past = query_strings(
&db,
r#"(query [:find ?r :as-of 1 :valid-at :any-valid-time :where [?e :role ?r]])"#,
);
assert!(
past.contains(&"user".to_string()),
"past role at tx 1 should be user"
);
}
#[test]
fn xtdb_valid_at_query() {
let db = Minigraf::in_memory().unwrap();
db.execute(r#"(transact {:valid-from "2023-01-01" :valid-to "2023-12-31"} [[:contract :status "active"]])"#)
.unwrap();
let n = count_results(
db.execute(r#"(query [:find ?s :valid-at "2023-06-01" :where [?e :status ?s]])"#)
.unwrap(),
);
assert_eq!(n, 1, "fact should be visible within valid-time range");
let n_after = count_results(
db.execute(r#"(query [:find ?s :valid-at "2024-01-01" :where [?e :status ?s]])"#)
.unwrap(),
);
assert_eq!(
n_after, 0,
"fact should not be visible after valid-to boundary"
);
}
#[test]
fn xtdb_not_excludes_matching_entities() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:alice :person true] [:alice :banned true]
[:bob :person true]
[:carol :person true] [:carol :banned true]
])"#,
)
.unwrap();
let unbanned = count_results(
db.execute(r#"(query [:find ?e :where [?e :person true] (not [?e :banned true])])"#)
.unwrap(),
);
assert_eq!(unbanned, 1, "only bob should appear (not banned)");
}
#[test]
fn xtdb_count_aggregate() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:a :tag "rust"] [:b :tag "rust"] [:c :tag "go"] [:d :tag "rust"]
])"#,
)
.unwrap();
match db
.execute(r#"(query [:find (count ?e) :where [?e :tag "rust"]])"#)
.unwrap()
{
QueryResult::QueryResults { results, .. } => {
assert!(!results.is_empty(), "count aggregate must return a result");
match results[0][0] {
Value::Integer(n) => assert_eq!(n, 3, "count of :tag rust should be 3"),
_ => panic!("expected integer count value"),
}
}
_ => panic!("expected QueryResults"),
}
}
#[test]
fn xtdb_recursive_ancestor_rule() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:alice :parent :bob]
[:bob :parent :carol]
[:carol :parent :dave]
])"#,
)
.unwrap();
db.execute(r#"(rule [(ancestor ?x ?y) [?x :parent ?y]])"#)
.unwrap();
db.execute(r#"(rule [(ancestor ?x ?z) [?x :parent ?y] (ancestor ?y ?z)])"#)
.unwrap();
let ancestors_of_alice = count_results(
db.execute(r#"(query [:find ?anc :where (ancestor :alice ?anc)])"#)
.unwrap(),
);
assert_eq!(
ancestors_of_alice, 3,
"alice has 3 ancestors: bob, carol, dave"
);
}