#![cfg(not(target_arch = "wasm32"))]
use minigraf::{BindValue, Minigraf, QueryResult, Value};
fn count_results(r: QueryResult) -> usize {
match r {
QueryResult::QueryResults { results, .. } => results.len(),
_ => 0,
}
}
#[test]
fn datomic_entity_attributes_are_independent_facts() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:user42 :user/name "Jane"]
[:user42 :user/email "jane@example.com"]
[:user42 :user/role :admin]
])"#,
)
.unwrap();
assert_eq!(
count_results(
db.execute(r#"(query [:find ?n :where [?e :user/name ?n]])"#)
.unwrap()
),
1,
"name fact must be independently queryable"
);
assert_eq!(
count_results(
db.execute(r#"(query [:find ?em :where [?e :user/email ?em]])"#)
.unwrap()
),
1,
"email fact must be independently queryable"
);
}
#[test]
fn datomic_multiple_entities_same_attribute() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:article1 :tag "rust"]
[:article2 :tag "database"]
[:article3 :tag "embedded"]
])"#,
)
.unwrap();
let tags = count_results(
db.execute(r#"(query [:find ?e ?t :where [?e :tag ?t]])"#)
.unwrap(),
);
assert_eq!(tags, 3, "all 3 tag facts must be independently queryable");
}
#[test]
fn datomic_transaction_time_as_of() {
let db = Minigraf::in_memory().unwrap();
db.execute(r#"(transact [[:inv :qty 10]])"#).unwrap();
db.execute(r#"(retract [[:inv :qty 10]])"#).unwrap();
db.execute(r#"(transact [[:inv :qty 20]])"#).unwrap();
match db
.execute(r#"(query [:find ?q :as-of 1 :valid-at :any-valid-time :where [?e :qty ?q]])"#)
.unwrap()
{
QueryResult::QueryResults { results, .. } => {
let qty: Vec<i64> = results
.into_iter()
.flatten()
.filter_map(|v| match v {
Value::Integer(n) => Some(n),
_ => None,
})
.collect();
assert!(qty.contains(&10), "as-of tx 1: qty must be 10");
}
_ => panic!("expected QueryResults"),
}
}
#[test]
fn datomic_retract_all_entity_facts() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:ghost :name "Ghost"]
[:ghost :age 100]
[:ghost :role "phantom"]
])"#,
)
.unwrap();
db.execute(
r#"(retract [
[:ghost :name "Ghost"]
[:ghost :age 100]
[:ghost :role "phantom"]
])"#,
)
.unwrap();
let n = count_results(
db.execute(r#"(query [:find ?e :where [?e :name ?n]])"#)
.unwrap(),
);
assert_eq!(n, 0, "fully-retracted entity must not appear in queries");
}
#[test]
fn datomic_multi_variable_find() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:p1 :person/name "Alice"] [:p1 :person/age 30]
[:p2 :person/name "Bob"] [:p2 :person/age 25]
])"#,
)
.unwrap();
match db
.execute(r#"(query [:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]])"#)
.unwrap()
{
QueryResult::QueryResults { results, .. } => {
assert_eq!(results.len(), 2, "should find 2 name+age pairs");
}
_ => panic!("expected QueryResults"),
}
}
#[test]
fn datomic_ground_value_binding() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:a :score 10] [:b :score 20] [:c :score 10] [:d :score 30]
])"#,
)
.unwrap();
let tens = count_results(
db.execute(r#"(query [:find ?e :where [?e :score 10]])"#)
.unwrap(),
);
assert_eq!(tens, 2, "entities with score=10: a and c");
}
#[test]
fn datomic_parameterized_query_prepared() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:x :val 42] [:y :val 7] [:z :val 42]
])"#,
)
.unwrap();
let prep = db
.prepare("(query [:find ?e :where [?e :val $target]])")
.unwrap();
let results_42 = count_results(
prep.execute(&[("target", BindValue::Val(Value::Integer(42)))])
.unwrap(),
);
let results_7 = count_results(
prep.execute(&[("target", BindValue::Val(Value::Integer(7)))])
.unwrap(),
);
assert_eq!(results_42, 2, "val=42: x and z");
assert_eq!(results_7, 1, "val=7: y only");
}
#[test]
fn datomic_named_rule_reuse() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:a :likes :b] [:b :likes :c] [:c :likes :a]
])"#,
)
.unwrap();
db.execute(r#"(rule [(likes-transitively ?x ?y) [?x :likes ?y]])"#)
.unwrap();
db.execute(r#"(rule [(likes-transitively ?x ?z) [?x :likes ?y] (likes-transitively ?y ?z)])"#)
.unwrap();
let all_pairs = count_results(
db.execute(r#"(query [:find ?x ?y :where (likes-transitively ?x ?y)])"#)
.unwrap(),
);
assert!(
all_pairs >= 3,
"transitive closure must find at least 3 pairs"
);
}
#[test]
fn datomic_predicate_expression_filter() {
let db = Minigraf::in_memory().unwrap();
db.execute(
r#"(transact [
[:a :age 25] [:b :age 35] [:c :age 15] [:d :age 40]
])"#,
)
.unwrap();
let adults = count_results(
db.execute(r#"(query [:find ?e :where [?e :age ?a] [(>= ?a 18)]])"#)
.unwrap(),
);
assert_eq!(adults, 3, "entities with age >= 18: a, b, d");
}