use motedb::types::Tensor;
use motedb::{types::Value, Database};
use tempfile::TempDir;
fn rows(result: motedb::StreamingQueryResult) -> Vec<Vec<Value>> {
use motedb::QueryResult;
match result.materialize().unwrap() {
QueryResult::Select { rows, .. } => rows,
_ => panic!("Expected Select result"),
}
}
#[test]
fn test_vector_index_create_and_search() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE docs (id INT PRIMARY KEY, embedding VECTOR(4))")
.unwrap();
for i in 0..20 {
let row = vec![
Value::Integer(i),
Value::tensor(Tensor::new(vec![
i as f32,
(i + 1) as f32,
(i + 2) as f32,
(i + 3) as f32,
])),
];
db.insert_row("docs", row).unwrap();
}
db.execute("CREATE VECTOR INDEX idx_emb ON docs(embedding)")
.unwrap();
db.wait_for_indexes_ready();
let query = vec![5.0_f32, 6.0, 7.0, 8.0];
let results = db.vector_search("idx_emb", &query, 5);
match results {
Ok(neighbors) => {
assert!(neighbors.len() <= 5, "Should return at most 5 results");
for i in 1..neighbors.len() {
assert!(
neighbors[i].1 >= neighbors[i - 1].1,
"Results should be sorted by distance"
);
}
}
Err(_) => {
}
}
}
#[test]
fn test_vector_index_stats() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE vecs (id INT PRIMARY KEY, v VECTOR(3))")
.unwrap();
for i in 0..5 {
let row = vec![
Value::Integer(i),
Value::tensor(Tensor::new(vec![i as f32, (i + 1) as f32, (i + 2) as f32])),
];
db.insert_row("vecs", row).unwrap();
}
db.execute("CREATE VECTOR INDEX idx_v ON vecs(v)").unwrap();
db.wait_for_indexes_ready();
let stats = db.vector_index_stats("idx_v");
assert!(stats.is_ok() || stats.is_err());
}
#[test]
fn test_text_index_create_and_search() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE articles (id INT PRIMARY KEY, content TEXT)")
.unwrap();
db.execute("INSERT INTO articles VALUES (1, 'Rust is a systems programming language')")
.unwrap();
db.execute("INSERT INTO articles VALUES (2, 'Python is popular for data science')")
.unwrap();
db.execute(
"INSERT INTO articles VALUES (3, 'Rust provides memory safety without garbage collection')",
)
.unwrap();
db.execute("CREATE TEXT INDEX idx_content ON articles(content)")
.unwrap();
db.wait_for_indexes_ready();
let results = db.text_search_ranked("idx_content", "Rust", 5);
match results {
Ok(hits) => {
assert!(hits.len() <= 5, "Should return at most 5 results");
}
Err(_) => {
}
}
}
#[test]
fn test_text_index_nonexistent() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
let result = db.text_search_ranked("nonexistent_idx", "test", 5);
assert!(result.is_err(), "Search on nonexistent index should error");
}
#[test]
fn test_ioctree_index_create() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE points (id INT PRIMARY KEY, location GEOMETRY, name TEXT)")
.unwrap();
for i in 0..5 {
let x = i as f64;
let y = i as f64 + 1.0;
let z = i as f64 + 2.0;
let row = vec![
Value::Integer(i),
Value::spatial(motedb::types::Geometry::Point3D(
motedb::types::Point3D::new(x, y, z),
)),
Value::text(format!("P{}", i)),
];
db.insert_row("points", row).unwrap();
}
let result = db.create_ioctree_index("points_location");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_ioctree_knn_search() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE pts (id INT PRIMARY KEY, pos GEOMETRY)")
.unwrap();
for i in 0..20 {
let x = i as f64 * 0.5;
let y = i as f64 * 0.5;
let z = i as f64 * 0.5;
let row = vec![
Value::Integer(i),
Value::spatial(motedb::types::Geometry::Point3D(
motedb::types::Point3D::new(x, y, z),
)),
];
db.insert_row("pts", row).unwrap();
}
let result = db.create_ioctree_index("pts_pos");
if result.is_ok() {
db.wait_for_indexes_ready();
match db.ioctree_knn_search("pts_pos", &motedb::types::Point3D::new(0.0, 0.0, 0.0), 3) {
Ok(neighbors) => {
assert!(neighbors.len() <= 3, "KNN should return at most 3 results");
}
Err(_) => {}
}
}
}
#[test]
fn test_vector_order_by_distance() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE items (id INT PRIMARY KEY, embedding VECTOR(3))")
.unwrap();
for i in 0..10 {
let row = vec![
Value::Integer(i),
Value::tensor(Tensor::new(vec![i as f32, 0.0, 0.0])),
];
db.insert_row("items", row).unwrap();
}
db.execute("CREATE VECTOR INDEX idx_emb ON items(embedding)")
.unwrap();
db.wait_for_indexes_ready();
let result = db.execute(
"SELECT id FROM items ORDER BY VECTOR_DISTANCE(embedding, '[3.0, 0.0, 0.0]') LIMIT 3",
);
match result {
Ok(r) => {
let r = rows(r);
assert!(r.len() <= 3);
}
Err(_) => {
}
}
}
#[test]
fn test_match_against_sql() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE docs (id INT PRIMARY KEY, content TEXT)")
.unwrap();
db.execute("INSERT INTO docs VALUES (1, 'database management system')")
.unwrap();
db.execute("INSERT INTO docs VALUES (2, 'machine learning algorithms')")
.unwrap();
db.execute("INSERT INTO docs VALUES (3, 'database optimization techniques')")
.unwrap();
db.execute("CREATE TEXT INDEX idx_doc ON docs(content)")
.unwrap();
db.wait_for_indexes_ready();
let result =
db.execute("SELECT id FROM docs WHERE MATCH_AGAINST(content, 'database') ORDER BY id");
match result {
Ok(r) => {
let r = rows(r);
assert!(r.len() >= 1, "MATCH_AGAINST should find at least 1 result");
}
Err(_) => {
}
}
}
#[test]
fn test_create_text_index_nonexistent_table() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
let result = db.execute("CREATE TEXT INDEX ghost_idx ON ghost_table(content)");
assert!(
result.is_err(),
"Text index on nonexistent table should error"
);
}
#[test]
fn test_create_vector_index_nonexistent_table() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
let result = db.execute("CREATE VECTOR INDEX ghost_idx ON ghost_table(embedding)");
assert!(
result.is_err(),
"Vector index on nonexistent table should error"
);
}