#![allow(non_snake_case)]
use triviumdb::database::{Config, Database, StorageMode};
const DIM: usize = 2;
fn tmp_db(name: &str) -> String {
let dir = std::env::temp_dir().join("triviumdb_test");
std::fs::create_dir_all(&dir).ok();
dir.join(format!("tql_{}", name))
.to_string_lossy()
.to_string()
}
fn cleanup(path: &str) {
for ext in &["", ".wal", ".vec", ".lock", ".flush_ok"] {
std::fs::remove_file(format!("{}{}", path, ext)).ok();
}
}
fn build_test_db(name: &str) -> (Database<f32>, String) {
let path = tmp_db(name);
cleanup(&path);
let config = Config {
dim: DIM,
storage_mode: StorageMode::Rom,
..Default::default()
};
let mut db = Database::<f32>::open_with_config(&path, config).unwrap();
db.insert(
&[1.0, 0.0],
serde_json::json!({"type": "person", "name": "Alice", "age": 30, "region": "cn"}),
)
.unwrap();
db.insert(
&[0.0, 1.0],
serde_json::json!({"type": "person", "name": "Bob", "age": 25, "region": "kr"}),
)
.unwrap();
db.insert(
&[0.5, 0.5],
serde_json::json!({"type": "event", "name": "Summit", "heat": 0.9, "region": "cn"}),
)
.unwrap();
db.insert(
&[0.3, 0.7],
serde_json::json!({"type": "event", "name": "Report", "heat": 0.3, "region": "jp"}),
)
.unwrap();
db.insert(
&[0.8, 0.2],
serde_json::json!({"type": "person", "name": "Carol", "age": 35, "region": "cn"}),
)
.unwrap();
db.link(1, 2, "knows", 1.0).unwrap(); db.link(1, 5, "knows", 1.0).unwrap(); db.link(2, 5, "reports_to", 1.0).unwrap(); db.link(1, 3, "authored", 1.0).unwrap(); db.link(5, 4, "authored", 1.0).unwrap();
(db, path)
}
#[test]
fn TQL_FIND_简单等值() {
let (db, path) = build_test_db("find_eq");
let results = db.tql(r#"FIND {type: "person"} RETURN *"#).unwrap();
assert_eq!(results.len(), 3, "应找到 Alice, Bob, Carol");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_FIND_操作符Gt() {
let (db, path) = build_test_db("find_gt");
let results = db.tql(r#"FIND {age: {$gt: 28}} RETURN *"#).unwrap();
assert_eq!(results.len(), 2, "age>28: Alice(30), Carol(35)");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_FIND_操作符In() {
let (db, path) = build_test_db("find_in");
let results = db
.tql(r#"FIND {region: {$in: ["cn", "kr"]}} RETURN *"#)
.unwrap();
assert_eq!(results.len(), 4, "cn: Alice,Summit,Carol; kr: Bob");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_FIND_带LIMIT() {
let (db, path) = build_test_db("find_limit");
let results = db.tql(r#"FIND {type: "person"} RETURN * LIMIT 2"#).unwrap();
assert_eq!(results.len(), 2);
drop(db);
cleanup(&path);
}
#[test]
fn TQL_FIND_带ORDER_BY() {
let (db, path) = build_test_db("find_order");
let results = db
.tql(r#"FIND {type: "event"} RETURN * ORDER BY _.heat DESC"#)
.unwrap();
assert_eq!(results.len(), 2);
let first = &results[0]["_"];
assert_eq!(first.payload["name"], "Summit");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_FIND_Or逻辑() {
let (db, path) = build_test_db("find_or");
let results = db
.tql(r#"FIND {$or: [{name: "Alice"}, {name: "Bob"}]} RETURN *"#)
.unwrap();
assert_eq!(results.len(), 2);
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_单跳() {
let (db, path) = build_test_db("match_single");
let results = db
.tql(r#"MATCH (a {name: "Alice"})-[:knows]->(b) RETURN b"#)
.unwrap();
assert_eq!(results.len(), 2, "Alice knows Bob and Carol");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_带WHERE() {
let (db, path) = build_test_db("match_where");
let results = db
.tql(r#"MATCH (a {name: "Alice"})-[:knows]->(b) WHERE b.age > 28 RETURN b"#)
.unwrap();
assert_eq!(results.len(), 1, "Only Carol has age > 28");
assert_eq!(results[0]["b"].payload["name"], "Carol");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_多跳() {
let (db, path) = build_test_db("match_multi");
let results = db
.tql(r#"MATCH (a {name: "Alice"})-[:knows]->(b)-[:reports_to]->(c) RETURN c"#)
.unwrap();
assert_eq!(results.len(), 1, "Alice->Bob->Carol via reports_to");
assert_eq!(results[0]["c"].payload["name"], "Carol");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_任意边() {
let (db, path) = build_test_db("match_any_edge");
let results = db
.tql(r#"MATCH (a {name: "Alice"})-[]->(b) RETURN b"#)
.unwrap();
assert_eq!(results.len(), 3);
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_多标签() {
let (db, path) = build_test_db("match_multi_label");
let results = db
.tql(r#"MATCH (a {name: "Alice"})-[:knows|authored]->(b) RETURN b"#)
.unwrap();
assert_eq!(results.len(), 3, "2 knows + 1 authored");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_可变长路径() {
let (db, path) = build_test_db("match_varlen");
let results = db
.tql(r#"MATCH (a {name: "Alice"})-[:knows*1..2]->(b) RETURN b"#)
.unwrap();
assert!(results.len() >= 2, "At least Bob and Carol via 1-hop knows");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_WHERE_MATCHES() {
let (db, path) = build_test_db("match_matches");
let results = db
.tql(r#"MATCH (a)-[:authored]->(e) WHERE e MATCHES {heat: {$gte: 0.5}} RETURN a, e"#)
.unwrap();
assert_eq!(results.len(), 1, "Only Summit has heat >= 0.5");
assert_eq!(results[0]["e"].payload["name"], "Summit");
assert_eq!(results[0]["a"].payload["name"], "Alice");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_MATCH_内联Mongo操作符() {
let (db, path) = build_test_db("match_inline_mongo");
let results = db
.tql(r#"MATCH (a {age: {$gte: 30}})-[:knows]->(b) RETURN a, b"#)
.unwrap();
assert_eq!(results.len(), 2, "Alice(age=30) knows Bob and Carol");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_SEARCH_基础() {
let (db, path) = build_test_db("search_basic");
let results = db
.tql(r#"SEARCH VECTOR [1.0, 0.0] TOP 3 RETURN *"#)
.unwrap();
assert!(results.len() <= 3);
let first = &results[0]["_"];
assert_eq!(first.payload["name"], "Alice");
drop(db);
cleanup(&path);
}
#[test]
fn TQL_SEARCH_带WHERE过滤() {
let (db, path) = build_test_db("search_where");
let results = db
.tql(r#"SEARCH VECTOR [0.5, 0.5] TOP 5 WHERE {type: "event"} RETURN *"#)
.unwrap();
for row in &results {
assert_eq!(row["_"].payload["type"], "event");
}
drop(db);
cleanup(&path);
}
#[test]
fn TQL_SEARCH_带EXPAND() {
let (db, path) = build_test_db("search_expand");
let results = db
.tql(r#"SEARCH VECTOR [1.0, 0.0] TOP 1 EXPAND [:knows*1..1] RETURN *"#)
.unwrap();
assert!(
results.len() >= 2,
"Should include Alice and her knows neighbors"
);
drop(db);
cleanup(&path);
}
#[test]
fn TQL_错误_语法解析() {
let (db, path) = build_test_db("err_parse");
let result = db.tql("INVALID QUERY");
assert!(result.is_err());
drop(db);
cleanup(&path);
}
#[test]
fn TQL_空结果_不报错() {
let (db, path) = build_test_db("empty_result");
let results = db.tql(r#"FIND {type: "nonexistent"} RETURN *"#).unwrap();
assert!(results.is_empty());
drop(db);
cleanup(&path);
}