#![cfg(feature = "vector-extension")]
use akar_common::types::Value;
use akar_main::{Connection, Database, SystemConfig};
use std::sync::Arc;
use std::sync::mpsc;
use std::time::Duration;
use tempfile::tempdir;
fn setup() -> (tempfile::TempDir, Arc<Database>, Connection) {
let dir = tempdir().unwrap();
let db = Arc::new(Database::new(dir.path().to_str().unwrap(), SystemConfig::default()).unwrap());
let conn = Connection::new(&db);
conn.query("CREATE NODE TABLE Memory (id INT64, content STRING, embedding FLOAT[], PRIMARY KEY (id))")
.expect("create Memory table");
(dir, db, conn)
}
#[test]
fn create_vector_index_twice_no_deadlock() {
let (_dir, _db, conn) = setup();
conn.query("CREATE VECTOR INDEX mem_vec ON (Memory.embedding) WITH (metric=cosine, dims=384)")
.expect("first create should succeed");
let (tx, rx) = mpsc::channel();
let handle = std::thread::spawn(move || {
let r = conn.query("CREATE VECTOR INDEX mem_vec ON (Memory.embedding) WITH (metric=cosine, dims=384)");
let _ = tx.send(r);
});
let result = match rx.recv_timeout(Duration::from_secs(15)) {
Ok(r) => r,
Err(_) => {
panic!("second CREATE VECTOR INDEX deadlocked on the catalog mutex");
}
};
handle.join().expect("worker thread panicked");
match result {
Err(e) => assert!(e.contains("already exists"), "expected 'already exists', got: {e}"),
Ok(_) => panic!("second CREATE VECTOR INDEX should have errored with 'already exists'"),
}
}
#[test]
fn vector_index_does_not_break_scalar_queries() {
let (_dir, _db, conn) = setup();
conn.query("CREATE VECTOR INDEX mem_vec ON (Memory.embedding) WITH (metric=cosine, dims=384)")
.expect("first create");
conn.query("CREATE VECTOR INDEX mem_vec_2 ON (Memory.embedding) WITH (metric=cosine, dims=384)")
.expect("second create on same column");
conn.query("CREATE (m:Memory {id: 1, content: 'hello', embedding: [0.1, 0.2]})")
.expect("insert row");
let res = conn
.query("MATCH (m:Memory) RETURN m.id, m.content")
.expect("scalar query");
let chunk = res.chunks.first().expect("one chunk");
assert_eq!(chunk.size, 1, "one row expected");
}
#[test]
fn complex_type_list_column_round_trips() {
let (_dir, _db, conn) = setup();
conn.query("CREATE (m:Memory {id: 1, content: 'hello', embedding: [0.1, 0.2, 0.3]})")
.expect("insert row");
let res = conn.query("MATCH (m:Memory) RETURN m.embedding").expect("list query");
let chunk = res.chunks.first().expect("one chunk");
assert_eq!(chunk.size, 1, "one row expected");
let val = chunk.get_value(0, 0).expect("embedding must not be null");
match val {
Value::List(items) => {
assert_eq!(items.len(), 3, "embedding has 3 elements, got {items:?}");
for item in &items {
assert!(
matches!(item, Value::Float(_) | Value::Double(_)),
"expected numeric embedding element, got {item:?}"
);
}
}
other => panic!("expected Value::List, got {other:?}"),
}
}
#[test]
fn complex_type_map_literal_returns_struct() {
let (_dir, _db, conn) = setup();
conn.query("CREATE (m:Memory {id: 1, content: 'hello', embedding: [0.1, 0.2]})")
.expect("insert row");
let res = conn
.query("MATCH (m:Memory) RETURN {id: m.id}")
.expect("map literal query");
let chunk = res.chunks.first().expect("one chunk");
assert_eq!(chunk.size, 1, "one row expected");
let val = chunk.get_value(0, 0).expect("map literal must not be null");
match val {
Value::Struct(entries) => {
assert_eq!(entries.len(), 1, "one field, got {entries:?}");
assert_eq!(entries[0].0, "id");
assert_eq!(entries[0].1, Value::Int64(1));
}
other => panic!("expected Value::Struct, got {other:?}"),
}
}
#[test]
fn complex_type_array_cosine_similarity_returns_double() {
let (_dir, _db, conn) = setup();
conn.query("CREATE (m:Memory {id: 1, content: 'hello', embedding: [1.0, 0.0]})")
.expect("insert row");
let res = conn
.query("MATCH (m:Memory) RETURN array_cosine_similarity(m.embedding, [1.0, 0.0])")
.expect("cosine query");
let chunk = res.chunks.first().expect("one chunk");
assert_eq!(chunk.size, 1, "one row expected");
let val = chunk.get_value(0, 0).expect("cosine similarity must not be null");
match val {
Value::Double(d) => assert!((d - 1.0).abs() < 1e-9, "expected ~1.0, got {d}"),
other => panic!("expected Value::Double, got {other:?}"),
}
}