use std::collections::BTreeMap;
use std::time::Duration;
use itertools::Itertools;
use log::debug;
use serde_json::json;
use smartstring::{LazyCompact, SmartString};
use crate::data::expr::Expr;
use crate::data::symb::Symbol;
use crate::data::value::DataValue;
use crate::fixed_rule::FixedRulePayload;
use crate::fts::{TokenizerCache, TokenizerConfig};
use crate::parse::SourceSpan;
use crate::runtime::callback::CallbackOp;
use crate::runtime::db::Poison;
use crate::{DbInstance, FixedRule, RegularTempStore, ScriptMutability};
#[test]
fn test_limit_offset() {
let db = DbInstance::default();
let res = db
.run_default("?[a] := a in [5,3,1,2,4] :limit 2")
.unwrap()
.into_json();
assert_eq!(res["rows"], json!([[3], [5]]));
let res = db
.run_default("?[a] := a in [5,3,1,2,4] :limit 2 :offset 1")
.unwrap()
.into_json();
assert_eq!(res["rows"], json!([[1], [3]]));
let res = db
.run_default("?[a] := a in [5,3,1,2,4] :limit 2 :offset 4")
.unwrap()
.into_json();
assert_eq!(res["rows"], json!([[4]]));
let res = db
.run_default("?[a] := a in [5,3,1,2,4] :limit 2 :offset 5")
.unwrap()
.into_json();
assert_eq!(res["rows"], json!([]));
}
#[test]
fn test_normal_aggr_empty() {
let db = DbInstance::default();
let res = db.run_default("?[count(a)] := a in []").unwrap().rows;
assert_eq!(res, vec![vec![DataValue::from(0)]]);
}
#[test]
fn test_meet_aggr_empty() {
let db = DbInstance::default();
let res = db.run_default("?[min(a)] := a in []").unwrap().rows;
assert_eq!(res, vec![vec![DataValue::Null]]);
let res = db
.run_default("?[min(a), count(a)] := a in []")
.unwrap()
.rows;
assert_eq!(res, vec![vec![DataValue::Null, DataValue::from(0)]]);
}
#[test]
fn test_layers() {
let _ = env_logger::builder().is_test(true).try_init();
let db = DbInstance::default();
let res = db
.run_default(
r#"
y[a] := a in [1,2,3]
x[sum(a)] := y[a]
x[sum(a)] := a in [4,5,6]
?[sum(a)] := x[a]
"#,
)
.unwrap()
.rows;
assert_eq!(res[0][0], DataValue::from(21.))
}
#[test]
fn test_conditions() {
let _ = env_logger::builder().is_test(true).try_init();
let db = DbInstance::default();
db.run_default(
r#"
{
?[code] <- [['a'],['b'],['c']]
:create airport {code}
}
{
?[fr, to, dist] <- [['a', 'b', 1.1], ['a', 'c', 0.5], ['b', 'c', 9.1]]
:create route {fr, to => dist}
}
"#,
)
.unwrap();
debug!("real test begins");
let res = db
.run_default(
r#"
r[code, dist] := *airport{code}, *route{fr: code, dist};
?[dist] := r['a', dist], dist > 0.5, dist <= 1.1;
"#,
)
.unwrap()
.rows;
assert_eq!(res[0][0], DataValue::from(1.1))
}
#[test]
fn test_classical() {
let _ = env_logger::builder().is_test(true).try_init();
let db = DbInstance::default();
let res = db
.run_default(
r#"
parent[] <- [['joseph', 'jakob'],
['jakob', 'isaac'],
['isaac', 'abraham']]
grandparent[gcld, gp] := parent[gcld, p], parent[p, gp]
?[who] := grandparent[who, 'abraham']
"#,
)
.unwrap()
.rows;
println!("{:?}", res);
assert_eq!(res[0][0], DataValue::from("jakob"))
}
#[test]
fn default_columns() {
let db = DbInstance::default();
db.run_default(
r#"
:create status {uid: String, ts default now() => quitted: Bool, mood: String}
"#,
)
.unwrap();
db.run_default(
r#"
?[uid, quitted, mood] <- [['z', true, 'x']]
:put status {uid => quitted, mood}
"#,
)
.unwrap();
}
#[test]
fn rm_does_not_need_all_keys() {
let db = DbInstance::default();
db.run_default(":create status {uid => mood}").unwrap();
assert!(db
.run_default("?[uid, mood] <- [[1, 2]] :put status {uid => mood}",)
.is_ok());
assert!(db
.run_default("?[uid, mood] <- [[2]] :put status {uid}",)
.is_err());
assert!(db
.run_default("?[uid, mood] <- [[3, 2]] :rm status {uid => mood}",)
.is_ok());
assert!(db.run_default("?[uid] <- [[1]] :rm status {uid}").is_ok());
}
#[test]
fn strict_checks_for_fixed_rules_args() {
let db = DbInstance::default();
let res = db.run_default(
r#"
r[] <- [[1, 2]]
?[] <~ PageRank(r[_, _])
"#,
);
println!("{:?}", res);
assert!(res.is_ok());
let db = DbInstance::default();
let res = db.run_default(
r#"
r[] <- [[1, 2]]
?[] <~ PageRank(r[a, b])
"#,
);
assert!(res.is_ok());
let db = DbInstance::default();
let res = db.run_default(
r#"
r[] <- [[1, 2]]
?[] <~ PageRank(r[a, a])
"#,
);
assert!(res.is_err());
}
#[test]
fn do_not_unify_underscore() {
let db = DbInstance::default();
let res = db
.run_default(
r#"
r1[] <- [[1, 'a'], [2, 'b']]
r2[] <- [[2, 'B'], [3, 'C']]
?[l1, l2] := r1[_ , l1], r2[_ , l2]
"#,
)
.unwrap()
.rows;
assert_eq!(res.len(), 4);
let res = db.run_default(
r#"
?[_] := _ = 1
"#,
);
assert!(res.is_err());
let res = db
.run_default(
r#"
?[x] := x = 1, _ = 1, _ = 2
"#,
)
.unwrap()
.rows;
assert_eq!(res.len(), 1);
}
#[test]
fn imperative_script() {
}
#[test]
fn returning_relations() {
let db = DbInstance::default();
let res = db
.run_default(
r#"
{:create _xxz {a}}
{?[a] := a in [5,4,1,2,3] :put _xxz {a}}
{?[a] := *_xxz[a], a % 2 == 0 :rm _xxz {a}}
{?[a] := *_xxz[b], a = b * 2}
"#,
)
.unwrap();
assert_eq!(res.into_json()["rows"], json!([[2], [6], [10]]));
let res = db.run_default(
r#"
{?[a] := *_xxz[b], a = b * 2}
"#,
);
assert!(res.is_err());
}
#[test]
fn test_trigger() {
let db = DbInstance::default();
db.run_default(":create friends {fr: Int, to: Int => data: Any}")
.unwrap();
db.run_default(":create friends.rev {to: Int, fr: Int => data: Any}")
.unwrap();
db.run_default(
r#"
::set_triggers friends
on put {
?[fr, to, data] := _new[fr, to, data]
:put friends.rev{ to, fr => data}
}
on rm {
?[fr, to] := _old[fr, to, data]
:rm friends.rev{ to, fr }
}
"#,
)
.unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,3]] :put friends {fr, to => data}")
.unwrap();
let ret = db
.export_relations(["friends", "friends.rev"].into_iter())
.unwrap();
let frs = ret.get("friends").unwrap();
assert_eq!(
vec![DataValue::from(1), DataValue::from(2), DataValue::from(3)],
frs.rows[0]
);
let frs_rev = ret.get("friends.rev").unwrap();
assert_eq!(
vec![DataValue::from(2), DataValue::from(1), DataValue::from(3)],
frs_rev.rows[0]
);
db.run_default(r"?[fr, to] <- [[1,2], [2,3]] :rm friends {fr, to}")
.unwrap();
let ret = db
.export_relations(["friends", "friends.rev"].into_iter())
.unwrap();
let frs = ret.get("friends").unwrap();
assert!(frs.rows.is_empty());
}
#[test]
fn test_callback() {
let db = DbInstance::default();
let mut collected = vec![];
let (_id, receiver) = db.register_callback("friends", None);
db.run_default(":create friends {fr: Int, to: Int => data: Any}")
.unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}")
.unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,4],[4,7,6]] :put friends {fr, to => data}")
.unwrap();
db.run_default(r"?[fr, to] <- [[1,9],[4,5]] :rm friends {fr, to}")
.unwrap();
std::thread::sleep(Duration::from_secs_f64(0.01));
while let Ok(d) = receiver.try_recv() {
collected.push(d);
}
let collected = collected;
assert_eq!(collected[0].0, CallbackOp::Put);
assert_eq!(collected[0].1.rows.len(), 2);
assert_eq!(collected[0].1.rows[0].len(), 3);
assert_eq!(collected[0].2.rows.len(), 0);
assert_eq!(collected[1].0, CallbackOp::Put);
assert_eq!(collected[1].1.rows.len(), 2);
assert_eq!(collected[1].1.rows[0].len(), 3);
assert_eq!(collected[1].2.rows.len(), 1);
assert_eq!(
collected[1].2.rows[0],
vec![DataValue::from(1), DataValue::from(2), DataValue::from(3)]
);
assert_eq!(collected[2].0, CallbackOp::Rm);
assert_eq!(collected[2].1.rows.len(), 2);
assert_eq!(collected[2].1.rows[0].len(), 2);
assert_eq!(collected[2].2.rows.len(), 1);
assert_eq!(collected[2].2.rows[0].len(), 3);
}
#[test]
fn test_update() {
let db = DbInstance::default();
db.run_default(":create friends {fr: Int, to: Int => a: Any, b: Any, c: Any}")
.unwrap();
db.run_default("?[fr, to, a, b, c] <- [[1,2,3,4,5]] :put friends {fr, to => a, b, c}")
.unwrap();
let res = db
.run_default("?[fr, to, a, b, c] := *friends{fr, to, a, b, c}")
.unwrap()
.into_json();
assert_eq!(res["rows"][0], json!([1, 2, 3, 4, 5]));
db.run_default("?[fr, to, b] <- [[1, 2, 100]] :update friends {fr, to => b}")
.unwrap();
let res = db
.run_default("?[fr, to, a, b, c] := *friends{fr, to, a, b, c}")
.unwrap()
.into_json();
assert_eq!(res["rows"][0], json!([1, 2, 3, 100, 5]));
}
#[test]
fn test_index() {
let db = DbInstance::default();
db.run_default(":create friends {fr: Int, to: Int => data: Any}")
.unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to, data}")
.unwrap();
assert!(db
.run_default("::index create friends:rev {to, no}")
.is_err());
db.run_default("::index create friends:rev {to, data}")
.unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}")
.unwrap();
db.run_default(r"?[fr, to] <- [[4,5]] :rm friends {fr, to}")
.unwrap();
let rels_data = db
.export_relations(["friends", "friends:rev"].into_iter())
.unwrap();
assert_eq!(
rels_data["friends"].clone().into_json()["rows"],
json!([[1, 2, 5], [6, 5, 7]])
);
assert_eq!(
rels_data["friends:rev"].clone().into_json()["rows"],
json!([[2, 5, 1], [5, 7, 6]])
);
let rels = db.run_default("::relations").unwrap();
assert_eq!(rels.rows[1][0], DataValue::from("friends:rev"));
assert_eq!(rels.rows[1][1], DataValue::from(3));
assert_eq!(rels.rows[1][2], DataValue::from("index"));
let cols = db.run_default("::columns friends:rev").unwrap();
assert_eq!(cols.rows.len(), 3);
let res = db
.run_default("?[fr, data] := *friends:rev{to: 2, fr, data}")
.unwrap();
assert_eq!(res.into_json()["rows"], json!([[1, 5]]));
let res = db
.run_default("?[fr, data] := *friends{to: 2, fr, data}")
.unwrap();
assert_eq!(res.into_json()["rows"], json!([[1, 5]]));
let expl = db
.run_default("::explain { ?[fr, data] := *friends{to: 2, fr, data} }")
.unwrap();
let joins = expl.into_json()["rows"]
.as_array()
.unwrap()
.iter()
.map(|row| row.as_array().unwrap()[5].clone())
.collect_vec();
assert!(joins.contains(&json!(":friends:rev")));
db.run_default("::index drop friends:rev").unwrap();
}
#[test]
fn test_json_objects() {
let db = DbInstance::default();
db.run_default("?[a] := a = {'a': 1}").unwrap();
db.run_default(
r"?[a] := a = {
'a': 1
}",
)
.unwrap();
}
#[test]
fn test_custom_rules() {
let db = DbInstance::default();
struct Custom;
impl FixedRule for Custom {
fn arity(
&self,
_options: &BTreeMap<SmartString<LazyCompact>, Expr>,
_rule_head: &[Symbol],
_span: SourceSpan,
) -> miette::Result<usize> {
Ok(1)
}
fn run(
&self,
payload: FixedRulePayload<'_, '_>,
out: &'_ mut RegularTempStore,
_poison: Poison,
) -> miette::Result<()> {
let rel = payload.get_input(0)?;
let mult = payload.integer_option("mult", Some(2))?;
for maybe_row in rel.iter()? {
let row = maybe_row?;
let mut sum = 0;
for col in row {
let d = col.get_int().unwrap_or(0);
sum += d;
}
sum *= mult;
out.put(vec![DataValue::from(sum)])
}
Ok(())
}
}
db.register_fixed_rule("SumCols".to_string(), Custom)
.unwrap();
let res = db
.run_default(
r#"
rel[] <- [[1,2,3,4],[5,6,7,8]]
?[x] <~ SumCols(rel[], mult: 100)
"#,
)
.unwrap();
assert_eq!(res.into_json()["rows"], json!([[1000], [2600]]));
}
#[test]
fn test_index_short() {
let db = DbInstance::default();
db.run_default(":create friends {fr: Int, to: Int => data: Any}")
.unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}")
.unwrap();
db.run_default("::index create friends:rev {to}").unwrap();
db.run_default(r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}")
.unwrap();
db.run_default(r"?[fr, to] <- [[4,5]] :rm friends {fr, to}")
.unwrap();
let rels_data = db
.export_relations(["friends", "friends:rev"].into_iter())
.unwrap();
assert_eq!(
rels_data["friends"].clone().into_json()["rows"],
json!([[1, 2, 5], [6, 5, 7]])
);
assert_eq!(
rels_data["friends:rev"].clone().into_json()["rows"],
json!([[2, 1], [5, 6]])
);
let rels = db.run_default("::relations").unwrap();
assert_eq!(rels.rows[1][0], DataValue::from("friends:rev"));
assert_eq!(rels.rows[1][1], DataValue::from(2));
assert_eq!(rels.rows[1][2], DataValue::from("index"));
let cols = db.run_default("::columns friends:rev").unwrap();
assert_eq!(cols.rows.len(), 2);
let expl = db
.run_default("::explain { ?[fr, data] := *friends{to: 2, fr, data} }")
.unwrap()
.into_json();
for row in expl["rows"].as_array().unwrap() {
println!("{}", row);
}
let joins = expl["rows"]
.as_array()
.unwrap()
.iter()
.map(|row| row.as_array().unwrap()[5].clone())
.collect_vec();
assert!(joins.contains(&json!(":friends:rev")));
let res = db
.run_default("?[fr, data] := *friends{to: 2, fr, data}")
.unwrap();
assert_eq!(res.into_json()["rows"], json!([[1, 5]]));
}
#[test]
fn test_multi_tx() {
let db = DbInstance::default();
let tx = db.multi_transaction(true);
tx.run_script(":create a {a}", Default::default()).unwrap();
tx.run_script("?[a] <- [[1]] :put a {a}", Default::default())
.unwrap();
assert!(tx.run_script(":create a {a}", Default::default()).is_err());
tx.run_script("?[a] <- [[2]] :put a {a}", Default::default())
.unwrap();
tx.run_script("?[a] <- [[3]] :put a {a}", Default::default())
.unwrap();
tx.commit().unwrap();
assert_eq!(
db.run_default("?[a] := *a[a]").unwrap().into_json()["rows"],
json!([[1], [2], [3]])
);
let db = DbInstance::default();
let tx = db.multi_transaction(true);
tx.run_script(":create a {a}", Default::default()).unwrap();
tx.run_script("?[a] <- [[1]] :put a {a}", Default::default())
.unwrap();
assert!(tx.run_script(":create a {a}", Default::default()).is_err());
tx.run_script("?[a] <- [[2]] :put a {a}", Default::default())
.unwrap();
tx.run_script("?[a] <- [[3]] :put a {a}", Default::default())
.unwrap();
tx.abort().unwrap();
assert!(db.run_default("?[a] := *a[a]").is_err());
}
#[test]
fn test_vec_types() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(":create a {k: String => v: <F32; 8>}")
.unwrap();
db.run_default("?[k, v] <- [['k', [1,2,3,4,5,6,7,8]]] :put a {k => v}")
.unwrap();
let res = db.run_default("?[k, v] := *a{k, v}").unwrap();
assert_eq!(
json!([1., 2., 3., 4., 5., 6., 7., 8.]),
res.into_json()["rows"][0][1]
);
let res = db
.run_default("?[v] <- [[vec([1,2,3,4,5,6,7,8])]]")
.unwrap();
assert_eq!(
json!([1., 2., 3., 4., 5., 6., 7., 8.]),
res.into_json()["rows"][0][0]
);
let res = db.run_default("?[v] <- [[rand_vec(5)]]").unwrap();
assert_eq!(5, res.into_json()["rows"][0][0].as_array().unwrap().len());
let res = db
.run_default(r#"
val[v] <- [[vec([1,2,3,4,5,6,7,8])]]
?[x,y,z] := val[v], x=l2_dist(v, v), y=cos_dist(v, v), nv = l2_normalize(v), z=ip_dist(nv, nv)
"#)
.unwrap();
println!("{}", res.into_json());
}
#[test]
fn test_vec_index_insertion() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(
r"
?[k, v, m] <- [['a', [1,2], true],
['b', [2,3], false]]
:create a {k: String => v: <F32; 2>, m: Bool}
",
)
.unwrap();
db.run_default(
r"
::hnsw create a:vec {
dim: 2,
m: 50,
dtype: F32,
fields: [v],
distance: L2,
ef_construction: 20,
filter: m,
#extend_candidates: true,
#keep_pruned_connections: true,
}",
)
.unwrap();
let res = db
.run_default("?[k] := *a:vec{layer: 0, fr_k, to_k}, k = fr_k or k = to_k")
.unwrap();
assert_eq!(res.rows.len(), 1);
println!("update!");
db.run_default(r#"?[k, m] <- [["a", false]] :update a {}"#)
.unwrap();
let res = db
.run_default("?[k] := *a:vec{layer: 0, fr_k, to_k}, k = fr_k or k = to_k")
.unwrap();
assert_eq!(res.rows.len(), 0);
println!("{}", res.into_json());
}
#[test]
fn test_vec_index() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(
r"
?[k, v] <- [['a', [1,2]],
['b', [2,3]],
['bb', [2,3]],
['c', [3,4]],
['x', [0,0.1]],
['a', [112,0]],
['b', [1,1]]]
:create a {k: String => v: <F32; 2>}
",
)
.unwrap();
db.run_default(
r"
::hnsw create a:vec {
dim: 2,
m: 50,
dtype: F32,
fields: [v],
distance: L2,
ef_construction: 20,
filter: k != 'k1',
#extend_candidates: true,
#keep_pruned_connections: true,
}",
)
.unwrap();
db.run_default(
r"
?[k, v] <- [
['a2', [1,25]],
['b2', [2,34]],
['bb2', [2,33]],
['c2', [2,32]],
['a2', [2,31]],
['b2', [1,10]]
]
:put a {k => v}
",
)
.unwrap();
println!("all links");
for (_, nrows) in db.export_relations(["a:vec"].iter()).unwrap() {
let nrows = nrows.rows;
for row in nrows {
println!("{} {} -> {} {}", row[0], row[1], row[4], row[7]);
}
}
let res = db
.run_default(
r"
#::explain {
?[dist, k, v] := ~a:vec{k, v | query: q, k: 2, ef: 20, bind_distance: dist}, q = vec([200, 34])
#}
",
)
.unwrap();
println!("results");
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{} {} {}", row[0], row[1], row[2]);
}
}
#[test]
fn test_fts_indexing() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {k: String => v: String}")
.unwrap();
db.run_default(
r"?[k, v] <- [['a', 'hello world!'], ['b', 'the world is round']] :put a {k => v}",
)
.unwrap();
db.run_default(
r"::fts create a:fts {
extractor: v,
tokenizer: Simple,
filters: [Lowercase, Stemmer('English'), Stopwords('en')]
}",
)
.unwrap();
db.run_default(
r"?[k, v] <- [
['b', 'the world is square!'],
['c', 'see you at the end of the world!'],
['d', 'the world is the world and makes the world go around']
] :put a {k => v}",
)
.unwrap();
let res = db
.run_default(
r"
?[word, src_k, offset_from, offset_to, position, total_length] :=
*a:fts{word, src_k, offset_from, offset_to, position, total_length}
",
)
.unwrap();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
println!("query");
let res = db
.run_default(r"?[k, v, s] := ~a:fts{k, v | query: 'world', k: 2, bind_score: s}")
.unwrap();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
}
#[test]
fn test_lsh_indexing2() {
for i in 1..10 {
let f = i as f64 / 10.;
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {k: String => v: String}")
.unwrap();
db.run_script(
r"::lsh create a:lsh {extractor: v, tokenizer: NGram, n_gram: 3, target_threshold: $t }",
BTreeMap::from([("t".into(), f.into())]),
ScriptMutability::Mutable
)
.unwrap();
db.run_default("?[k, v] <- [['a', 'ewiygfspeoighjsfcfxzdfncalsdf']] :put a {k => v}")
.unwrap();
let res = db
.run_default("?[k] := ~a:lsh{k | query: 'ewiygfspeoighjsfcfxzdfncalsdf', k: 1}")
.unwrap();
assert!(res.rows.len() > 0);
}
}
#[test]
fn test_lsh_indexing3() {
for i in 1..10 {
let f = i as f64 / 10.;
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create text {id: String, => text: String, url: String? default null, dt: Float default now(), dup_for: String? default null }")
.unwrap();
db.run_script(
r"::lsh create text:lsh {
extractor: text,
# extract_filter: is_null(dup_for),
tokenizer: NGram,
n_perm: 200,
target_threshold: $t,
n_gram: 7,
}",
BTreeMap::from([("t".into(), f.into())]),
ScriptMutability::Mutable,
)
.unwrap();
db.run_default(
"?[id, text] <- [['a', 'This function first generates 32 random bytes using the os.urandom function. It then base64 encodes these bytes using base64.urlsafe_b64encode, removes the padding, and decodes the result to a string.']] :put text {id, text}",
)
.unwrap();
let res = db
.run_default(
r#"?[id, dup_for] :=
~text:lsh{id: id, dup_for: dup_for, | query: "This function first generates 32 random bytes using the os.urandom function. It then base64 encodes these bytes using base64.urlsafe_b64encode, removes the padding, and decodes the result to a string.", }"#,
)
.unwrap();
assert!(res.rows.len() > 0);
println!("{}", res.into_json());
}
}
#[test]
fn filtering() {
let db = DbInstance::default();
let res = db
.run_default(
r"
{
?[x, y] <- [[1, 2]]
:create _rel {x => y}
:returning
}
{
?[x, y] := x = 1, *_rel{x, y: 3}, y = 2
}
",
)
.unwrap();
assert_eq!(0, res.rows.len());
let res = db.run_default(r"
{
?[x, u, y] <- [[1, 0, 2]]
:create _rel {x, u => y}
:returning
}
{
?[x, y] := x = 1, *_rel{x, y: 3}, y = 2
}
")
.unwrap();
assert_eq!(0, res.rows.len());
}
#[test]
fn test_lsh_indexing4() {
for i in 1..10 {
let f = i as f64 / 10.;
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {k: String => v: String}")
.unwrap();
db.run_script(
r"::lsh create a:lsh {extractor: v, tokenizer: NGram, n_gram: 3, target_threshold: $t }",
BTreeMap::from([("t".into(), f.into())]),
ScriptMutability::Mutable
)
.unwrap();
db.run_default("?[k, v] <- [['a', 'ewiygfspeoighjsfcfxzdfncalsdf']] :put a {k => v}")
.unwrap();
db.run_default("?[k] <- [['a']] :rm a {k}").unwrap();
let res = db
.run_default("?[k] := ~a:lsh{k | query: 'ewiygfspeoighjsfcfxzdfncalsdf', k: 1}")
.unwrap();
assert!(res.rows.len() == 0);
}
}
#[test]
fn test_lsh_indexing() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {k: String => v: String}")
.unwrap();
db.run_default(
r"?[k, v] <- [['a', 'hello world!'], ['b', 'the world is round']] :put a {k => v}",
)
.unwrap();
db.run_default(
r"::lsh create a:lsh {extractor: v, tokenizer: Simple, n_gram: 3, target_threshold: 0.3 }",
)
.unwrap();
db.run_default(
r"?[k, v] <- [
['b', 'the world is square!'],
['c', 'see you at the end of the world!'],
['d', 'the world is the world and makes the world go around'],
['e', 'the world is the world and makes the world not go around']
] :put a {k => v}",
)
.unwrap();
let res = db.run_default("::columns a:lsh").unwrap();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
let _res = db
.run_default(
r"
?[src_k, hash] :=
*a:lsh{src_k, hash}
",
)
.unwrap();
let _res = db
.run_default(
r"
?[k, minhash] :=
*a:lsh:inv{k, minhash}
",
)
.unwrap();
let res = db
.run_default(
r"
?[k, v] := ~a:lsh{k, v |
query: 'see him at the end of the world',
}
",
)
.unwrap();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
let res = db.run_default("::indices a").unwrap();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
db.run_default(r"::lsh drop a:lsh").unwrap();
}
#[test]
fn test_insertions() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {k => v: <F32; 1536> default rand_vec(1536)}")
.unwrap();
db.run_default(r"?[k] <- [[1]] :put a {k}").unwrap();
db.run_default(r"?[k, v] := *a{k, v}").unwrap();
db.run_default(
r"::hnsw create a:i {
fields: [v], dim: 1536, ef: 16, filter: k % 3 == 0,
m: 32
}",
)
.unwrap();
db.run_default(r"?[count(fr_k)] := *a:i{fr_k}").unwrap();
db.run_default(r"?[k] <- [[1]] :put a {k}").unwrap();
db.run_default(r"?[k] := k in int_range(300) :put a {k}")
.unwrap();
let res = db
.run_default(
r"?[dist, k] := ~a:i{k | query: v, bind_distance: dist, k:10, ef: 50, filter: k % 2 == 0, radius: 245}, *a{k: 96, v}",
)
.unwrap();
println!("results");
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{} {}", row[0], row[1]);
}
}
#[test]
fn tokenizers() {
let tokenizers = TokenizerCache::default();
let tokenizer = tokenizers
.get(
"simple",
&TokenizerConfig {
name: "Simple".into(),
args: vec![],
},
&[],
)
.unwrap();
let mut token_stream = tokenizer.token_stream("It is closer to Apache Lucene than to Elasticsearch or Apache Solr in the sense it is not an off-the-shelf search engine server, but rather a crate that can be used to build such a search engine.");
while let Some(token) = token_stream.next() {
println!("Token {:?}", token.text);
}
println!("XXXXXXXXXXXXX");
let tokenizer = tokenizers
.get(
"cangjie",
&TokenizerConfig {
name: "Cangjie".into(),
args: vec![],
},
&[],
)
.unwrap();
let mut token_stream = tokenizer.token_stream("这个产品Finchat.io是一个相对比较有特色的文档问答类网站,它集成了750多家公司的经融数据。感觉是把财报等数据借助Embedding都向量化了,然后接入ChatGPT进行对话。");
while let Some(token) = token_stream.next() {
println!("Token {:?}", token.text);
}
}
#[test]
fn multi_index_vec() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(
r#"
:create product {
id
=>
name,
description,
price,
name_vec: <F32; 1>,
description_vec: <F32; 1>
}
"#,
)
.unwrap();
db.run_default(
r#"
::hnsw create product:semantic{
fields: [name_vec, description_vec],
dim: 1,
ef: 16,
m: 32,
}
"#,
)
.unwrap();
db.run_default(
r#"
?[id, name, description, price, name_vec, description_vec] <- [[1, "name", "description", 100, [1], [1]]]
:put product {id => name, description, price, name_vec, description_vec}
"#,
).unwrap();
let res = db.run_default("::indices product").unwrap();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
}
#[test]
fn ensure_not() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(
r"
%ignore_error { :create id_alloc{id: Int => next_id: Int, last_id: Int}}
%ignore_error {
?[id, next_id, last_id] <- [[0, 1, 1000]];
:ensure_not id_alloc{id => next_id, last_id}
}
",
)
.unwrap();
}
#[test]
fn insertion() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {x => y}").unwrap();
assert!(db
.run_default(r"?[x, y] <- [[1, 2]] :insert a {x => y}",)
.is_ok());
assert!(db
.run_default(r"?[x, y] <- [[1, 3]] :insert a {x => y}",)
.is_err());
}
#[test]
fn deletion() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r":create a {x => y}").unwrap();
assert!(db.run_default(r"?[x] <- [[1]] :delete a {x}").is_err());
assert!(db
.run_default(r"?[x, y] <- [[1, 2]] :insert a {x => y}",)
.is_ok());
db.run_default(r"?[x] <- [[1]] :delete a {x}").unwrap();
}
#[test]
fn returning() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(":create a {x => y}").unwrap();
let res = db
.run_default(r"?[x, y] <- [[1, 2]] :insert a {x => y} ")
.unwrap();
assert_eq!(res.into_json()["rows"], json!([["OK"]]));
let res = db
.run_default(r"?[x, y] <- [[1, 3], [2, 4]] :returning :put a {x => y} ")
.unwrap();
assert_eq!(
res.into_json()["rows"],
json!([["inserted", 1, 3], ["inserted", 2, 4], ["replaced", 1, 2]])
);
let res = db
.run_default(r"?[x] <- [[1], [4]] :returning :rm a {x} ")
.unwrap();
assert_eq!(
res.into_json()["rows"],
json!([
["requested", 1, null],
["requested", 4, null],
["deleted", 1, 3]
])
);
db.run_default(r":create todo{id:Uuid default rand_uuid_v1() => label: String, done: Bool}")
.unwrap();
let res = db
.run_default(r"?[label,done] <- [['milk',false]] :put todo{label,done} :returning")
.unwrap();
assert_eq!(res.rows[0].len(), 4);
for title in res.headers.iter() {
print!("{} ", title);
}
println!();
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
}
#[test]
fn parser_corner_case() {
let db = DbInstance::new("mem", "", "").unwrap();
db.run_default(r#"?[x] := x = 1 or x = 2"#).unwrap();
db.run_default(r#"?[C] := C = 1 orx[C] := C = 1"#).unwrap();
db.run_default(r#"?[C] := C = true, C inx[C] := C = 1"#)
.unwrap();
db.run_default(r#"?[k] := k in int_range(300)"#).unwrap();
db.run_default(r#"ywcc[a] <- [[1]] noto[A] := ywcc[A] ?[A] := noto[A]"#)
.unwrap();
}
#[test]
fn as_store_in_imperative_script() {
let db = DbInstance::new("mem", "", "").unwrap();
let res = db
.run_default(
r#"
{ ?[x, y, z] <- [[1, 2, 3], [4, 5, 6]] } as _store
{ ?[x, y, z] := *_store{x, y, z} }
"#,
)
.unwrap();
assert_eq!(res.into_json()["rows"], json!([[1, 2, 3], [4, 5, 6]]));
let res = db
.run_default(
r#"
{
?[y] <- [[1], [2], [3]]
:create a {x default rand_uuid_v1() => y}
:returning
} as _last
{
?[x] := *_last{_kind: 'inserted', x}
}
"#,
)
.unwrap();
assert_eq!(3, res.rows.len());
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
assert!(db
.run_default(
r#"
{
?[x, x] := x = 1
} as _last
"#
)
.is_err());
let res = db
.run_default(
r#"
{
x[y] <- [[1], [2], [3]]
?[sum(y)] := x[y]
} as _last
{
?[sum_y] := *_last{sum_y}
}
"#,
)
.unwrap();
assert_eq!(1, res.rows.len());
for row in res.into_json()["rows"].as_array().unwrap() {
println!("{}", row);
}
}
#[test]
fn update_shall_not_destroy_values() {
let db = DbInstance::default();
db.run_default(r"?[x, y] <- [[1, 2]] :create z {x => y default 0}")
.unwrap();
let r = db.run_default(r"?[x, y] := *z {x, y}").unwrap();
assert_eq!(r.into_json()["rows"], json!([[1, 2]]));
db.run_default(r"?[x] <- [[1]] :update z {x}").unwrap();
let r = db.run_default(r"?[x, y] := *z {x, y}").unwrap();
assert_eq!(r.into_json()["rows"], json!([[1, 2]]));
}
#[test]
fn update_shall_work() {
let db = DbInstance::default();
db.run_default(r"?[x, y, z] <- [[1, 2, 3]] :create z {x => y, z}")
.unwrap();
let r = db.run_default(r"?[x, y, z] := *z {x, y, z}").unwrap();
assert_eq!(r.into_json()["rows"], json!([[1, 2, 3]]));
db.run_default(r"?[x, y] <- [[1, 4]] :update z {x, y}")
.unwrap();
let r = db.run_default(r"?[x, y, z] := *z {x, y, z}").unwrap();
assert_eq!(r.into_json()["rows"], json!([[1, 4, 3]]));
}
#[test]
fn sysop_in_imperatives() {
let script = r#"
{
:create cm_src {
aid: String =>
title: String,
author: String?,
kind: String,
url: String,
domain: String?,
pub_time: Float?,
dt: Float default now(),
weight: Float default 1,
}
}
{
:create cm_txt {
tid: String =>
aid: String,
tag: String,
follows_tid: String?,
dup_for: String?,
text: String,
info_amount: Int,
}
}
{
:create cm_seg {
sid: String =>
tid: String,
tag: String,
part: Int,
text: String,
vec: <F32; 1536>,
}
}
{
::hnsw create cm_seg:vec {
dim: 1536,
m: 50,
dtype: F32,
fields: vec,
distance: Cosine,
ef: 100,
}
}
{
::lsh create cm_txt:lsh {
extractor: text,
extract_filter: is_null(dup_for),
tokenizer: NGram,
n_perm: 200,
target_threshold: 0.5,
n_gram: 7,
}
}
{::relations}
"#;
let db = DbInstance::default();
db.run_default(script).unwrap();
}
#[test]
fn puts() {
let db = DbInstance::default();
db.run_default(
r"
:create cm_txt {
tid: String =>
aid: String,
tag: String,
follows_tid: String? default null,
for_qs: [String] default [],
dup_for: String? default null,
text: String,
seg_vecs: [<F32; 1536>],
seg_pos: [(Int, Int)],
format: String default 'text',
info_amount: Int,
}
",
)
.unwrap();
db.run_default(
r"
?[tid, aid, tag, text, info_amount, dup_for, seg_vecs, seg_pos] := dup_for = null,
tid = 'x', aid = 'y', tag = 'z', text = 'w', info_amount = 12,
follows_tid = null, for_qs = [], format = 'x',
seg_vecs = [], seg_pos = [[0, 10]]
:put cm_txt {tid, aid, tag, text, info_amount, seg_vecs, seg_pos, dup_for}
",
)
.unwrap();
}
#[test]
fn short_hand() {
let db = DbInstance::default();
db.run_default(r":create x {x => y, z}").unwrap();
db.run_default(r"?[x, y, z] <- [[1, 2, 3]] :put x {}")
.unwrap();
let r = db.run_default(r"?[x, y, z] := *x {x, y, z}").unwrap();
assert_eq!(r.into_json()["rows"], json!([[1, 2, 3]]));
}
#[test]
fn param_shorthand() {
let db = DbInstance::default();
db.run_script(
r"
?[] <- [[$x, $y, $z]]
:create x {}
",
BTreeMap::from([
("x".to_string(), DataValue::from(1)),
("y".to_string(), DataValue::from(2)),
("z".to_string(), DataValue::from(3)),
]),
ScriptMutability::Mutable,
)
.unwrap();
let res = db.run_default(r"?[x, y, z] := *x {x, y, z}");
assert_eq!(res.unwrap().into_json()["rows"], json!([[1, 2, 3]]));
}
#[test]
fn crashy_imperative() {
let db = DbInstance::default();
db.run_default(
r"
{:create _test {a}}
%loop
%if { len[count(x)] := *_test[x]; ?[x] := len[z], x = z >= 10 }
%then %return _test
%end
{ ?[a] := a = rand_uuid_v1(); :put _test {a} }
%end
",
)
.unwrap();
}
#[test]
fn hnsw_index() {
let db = DbInstance::default();
db.run_default(
r#"
:create beliefs {
belief_id: Uuid,
character_id: Uuid,
belief: String,
last_accessed_at: Validity default [floor(now()), true],
=>
details: String default "",
parent_belief_id: Uuid? default null,
valence: Float default 0,
aspects: [(String, Float, String, String)] default [],
belief_embedding: <F32; 768>,
details_embedding: <F32; 768>,
}
"#,
)
.unwrap();
db.run_default(
r#"
::hnsw create beliefs:embedding_space {
dim: 768,
m: 50,
dtype: F32,
fields: [belief_embedding, details_embedding],
distance: Cosine,
ef_construction: 20,
extend_candidates: false,
keep_pruned_connections: false,
}
"#,
)
.unwrap();
db.run_default(r#"
?[belief_id, character_id, belief, belief_embedding, details_embedding] <- [[rand_uuid_v1(), rand_uuid_v1(), "test", rand_vec(768), rand_vec(768)]]
:put beliefs {}
"#).unwrap();
let res = db.run_default(r#"
?[belief, valence, dist, character_id, vector] := ~beliefs:embedding_space{ belief, valence, character_id |
query: rand_vec(768),
k: 100,
ef: 20,
radius: 1.0,
bind_distance: dist,
bind_vector: vector
}
:order -valence
:order dist
"#).unwrap();
println!("{}", res.into_json()["rows"][0][4]);
}
#[test]
fn fts_drop() {
let db = DbInstance::default();
db.run_default(
r#"
:create entity {name}
"#,
)
.unwrap();
db.run_default(
r#"
::fts create entity:fts_index { extractor: name,
tokenizer: Simple, filters: [Lowercase]
}
"#,
)
.unwrap();
db.run_default(r#"
::fts drop entity:fts_index
"#).unwrap();
}