use crate::{StatedClaim, YantrikDB, LEARNED_CLAIM_EXTRACTOR};
fn rec(db: &YantrikDB, text: &str) -> String {
db.record_text(
text,
"semantic",
0.5,
0.0,
604800.0,
&serde_json::json!({}),
"default",
0.8,
"general",
"user",
None,
)
.unwrap()
}
fn state(db: &YantrikDB, rid: &str, src: &str, rel: &str, dst: &str) {
let report = db
.attach_claims(
rid,
&[StatedClaim {
src: src.into(),
rel_type: rel.into(),
dst: dst.into(),
polarity: 1,
valid_from: None,
valid_to: None,
}],
)
.unwrap();
assert_eq!(report.accepted.len(), 1, "{:?}", report.rejected);
}
fn learned_claims(db: &YantrikDB, src: &str) -> Vec<(String, String, String)> {
let conn = db.conn();
let mut stmt = conn
.prepare("SELECT src, rel_type, dst FROM claims WHERE src = ?1 AND extractor = ?2 AND tombstoned = 0")
.unwrap();
stmt.query_map(rusqlite::params![src, LEARNED_CLAIM_EXTRACTOR], |r| {
Ok((r.get(0)?, r.get(1)?, r.get(2)?))
})
.unwrap()
.map(|r| r.unwrap())
.collect()
}
#[test]
fn two_distinct_stated_pairs_promote_a_template_that_extracts_from_plain_writes() {
let db = YantrikDB::with_default(":memory:").unwrap();
let a = rec(&db, "Dana mentors Priya on the data platform.");
state(&db, &a, "Dana", "mentors", "Priya");
let after_one = db.learned_relation_patterns("default").unwrap();
assert_eq!(after_one.len(), 1);
assert_eq!(
(
after_one[0].phrase.as_str(),
after_one[0].pair_count,
after_one[0].active
),
("mentors", 1, false)
);
let b = rec(&db, "Kim mentors Alex during onboarding.");
state(&db, &b, "Kim", "mentors", "Alex");
let after_two = db.learned_relation_patterns("default").unwrap();
assert_eq!(
(after_two[0].pair_count, after_two[0].active),
(2, true),
"{after_two:?}"
);
rec(&db, "Sam mentors Jordan on release engineering.");
db.apply_pending_ops_once(100).unwrap();
assert_eq!(
learned_claims(&db, "Sam"),
vec![(
"Sam".to_string(),
"mentors".to_string(),
"Jordan".to_string()
)]
);
let conn = db.conn();
let heuristic: i64 = conn
.query_row(
"SELECT COUNT(*) FROM claims WHERE src = 'Sam' AND extractor = 'heuristic_v1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(heuristic, 0);
}
#[test]
fn restating_one_pair_never_promotes() {
let db = YantrikDB::with_default(":memory:").unwrap();
for _ in 0..3 {
let rid = rec(&db, "Dana mentors Priya on the data platform.");
state(&db, &rid, "Dana", "mentors", "Priya");
}
let patterns = db.learned_relation_patterns("default").unwrap();
assert_eq!((patterns[0].pair_count, patterns[0].active), (1, false));
rec(&db, "Sam mentors Jordan on release engineering.");
db.apply_pending_ops_once(100).unwrap();
assert!(learned_claims(&db, "Sam").is_empty());
}
#[test]
fn templates_are_namespace_scoped_and_forgettable() {
let db = YantrikDB::with_default(":memory:").unwrap();
for (s, o, text) in [
("Dana", "Priya", "Dana mentors Priya."),
("Kim", "Alex", "Kim mentors Alex."),
] {
let rid = db
.record_text(
text,
"semantic",
0.5,
0.0,
604800.0,
&serde_json::json!({}),
"tenant_a",
0.8,
"general",
"user",
None,
)
.unwrap();
state(&db, &rid, s, "mentors", o);
}
assert!(db.learned_relation_patterns("tenant_a").unwrap()[0].active);
assert!(db.learned_relation_patterns("default").unwrap().is_empty());
rec(&db, "Sam mentors Jordan.");
db.apply_pending_ops_once(100).unwrap();
assert!(learned_claims(&db, "Sam").is_empty());
assert_eq!(db.forget_learned_relation_patterns("tenant_a").unwrap(), 1);
assert!(db.learned_relation_patterns("tenant_a").unwrap().is_empty());
}
#[test]
fn function_word_windows_teach_nothing() {
let db = YantrikDB::with_default(":memory:").unwrap();
for (s, o, text) in [
("Pranab", "Vim", "Pranab is a Vim user."),
("Maria", "Emacs", "Maria is a Emacs user."),
] {
let rid = rec(&db, text);
state(&db, &rid, s, "uses", o);
}
assert!(db.learned_relation_patterns("default").unwrap().is_empty());
}
#[test]
fn v51_migration_creates_the_template_tables_on_an_upgraded_store() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::with_default(path).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute_batch(
"DROP TABLE learned_relation_pattern_support; DROP TABLE learned_relation_patterns; \
INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '50');",
)
.unwrap();
}
let db = YantrikDB::with_default(path).unwrap();
let rid = rec(&db, "Dana mentors Priya.");
state(&db, &rid, "Dana", "mentors", "Priya");
assert_eq!(db.learned_relation_patterns("default").unwrap().len(), 1);
}
#[test]
fn encrypted_stores_mine_nothing() {
let key = [7u8; 32];
let db = YantrikDB::new_encrypted(":memory:", 8, &key).unwrap();
let emb: Vec<f32> = (0..8).map(|i| (i as f32 + 1.0) / 8.0).collect();
for (s, o, text) in [
("Dana", "Priya", "Dana mentors Priya."),
("Kim", "Alex", "Kim mentors Alex."),
] {
let rid = db
.record(
text,
"semantic",
0.5,
0.0,
604800.0,
&serde_json::json!({}),
&emb,
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
state(&db, &rid, s, "mentors", o);
}
assert!(db.learned_relation_patterns("default").unwrap().is_empty());
}