use motedb::{Database, types::Value, sql::QueryResult};
use tempfile::TempDir;
fn create_db() -> (Database, TempDir) {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path().join("test.mote")).unwrap();
(db, dir)
}
fn rows(db: &Database, sql: &str) -> Vec<Vec<Value>> {
match db.execute(sql).unwrap().materialize().unwrap() {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
}
}
fn exec(db: &Database, sql: &str) -> QueryResult {
db.execute(sql).unwrap().materialize().unwrap()
}
#[test]
fn test_vector_insert_and_select() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE items (id INT, embedding VECTOR(4))").unwrap();
db.execute("INSERT INTO items (id, embedding) VALUES (1, [1.0, 0.0, 0.0, 0.0])").unwrap();
db.execute("INSERT INTO items (id, embedding) VALUES (2, [0.0, 1.0, 0.0, 0.0])").unwrap();
db.execute("INSERT INTO items (id, embedding) VALUES (3, [0.9, 0.1, 0.0, 0.0])").unwrap();
let r = rows(&db, "SELECT id FROM items ORDER BY id");
assert_eq!(r.len(), 3);
}
#[test]
fn test_vector_l2_distance() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE v (id INT, emb VECTOR(3))").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (1, [1.0, 0.0, 0.0])").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (2, [0.0, 1.0, 0.0])").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (3, [0.0, 0.0, 1.0])").unwrap();
let r = rows(&db, "SELECT id FROM v WHERE id != 1 ORDER BY emb <-> [1.0, 0.0, 0.0]");
assert_eq!(r.len(), 2);
}
#[test]
fn test_vector_cosine_distance() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE v (id INT, emb VECTOR(3))").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (1, [1.0, 0.0, 0.0])").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (2, [0.5, 0.5, 0.0])").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (3, [-1.0, 0.0, 0.0])").unwrap();
let r = rows(&db, "SELECT id FROM v ORDER BY emb <=> [1.0, 0.0, 0.0]");
assert_eq!(r.len(), 3);
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[2][0], Value::Integer(3));
}
#[test]
fn test_vector_dot_product() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE v (id INT, emb VECTOR(3))").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (1, [1.0, 2.0, 3.0])").unwrap();
let r = rows(&db, "SELECT emb <#> [1.0, 1.0, 1.0] AS dp FROM v");
assert_eq!(r.len(), 1);
if let Value::Float(dp) = r[0][0] {
let diff = (dp - 6.0).abs();
assert!(diff < 0.01, "dot product should be 6.0, got {}", dp);
}
}
#[test]
fn test_vector_zero_vector_cosine() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE v (id INT, emb VECTOR(2))").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (1, [0.0, 0.0])").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (2, [1.0, 0.0])").unwrap();
let r = rows(&db, "SELECT emb <=> [1.0, 0.0] AS dist FROM v WHERE id = 1");
assert_eq!(r.len(), 1);
if let Value::Float(dist) = r[0][0] {
assert!(!dist.is_nan() && !dist.is_infinite(), "distance should be finite, got {}", dist);
}
}
#[test]
fn test_vector_dimension_mismatch() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE v (id INT, emb VECTOR(3))").unwrap();
db.execute("INSERT INTO v (id, emb) VALUES (1, [1.0, 0.0, 0.0])").unwrap();
let result = db.execute("SELECT emb <-> [1.0, 0.0] AS dist FROM v");
let _ = result.map(|s| s.materialize());
}
#[test]
fn test_spatial_insert_and_select() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE poi (id INT, location GEOMETRY)").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (1, POINT(1.0, 2.0))").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (2, POINT(3.0, 4.0))").unwrap();
let r = rows(&db, "SELECT id FROM poi ORDER BY id");
assert_eq!(r.len(), 2);
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[1][0], Value::Integer(2));
}
#[test]
fn test_spatial_st_distance() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE poi (id INT, location GEOMETRY)").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (1, POINT(0.0, 0.0))").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (2, POINT(3.0, 4.0))").unwrap();
let r = rows(&db, "SELECT ST_DISTANCE(location, 0.0, 0.0) AS dist FROM poi WHERE id = 2");
assert_eq!(r.len(), 1);
if let Value::Float(dist) = r[0][0] {
let diff = (dist - 5.0).abs();
assert!(diff < 0.01, "ST_DISTANCE should be 5.0, got {}", dist);
}
}
#[test]
fn test_spatial_st_distance_3d() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE poi (id INT, location GEOMETRY)").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (1, POINT3D(1.0, 2.0, 2.0))").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (2, POINT3D(4.0, 6.0, 14.0))").unwrap();
let r = rows(&db, "SELECT ST_DISTANCE_3D(location, 1.0, 2.0, 2.0) AS dist FROM poi WHERE id = 2");
assert_eq!(r.len(), 1);
if let Value::Float(dist) = r[0][0] {
let diff = (dist - 13.0).abs();
assert!(diff < 0.01, "ST_DISTANCE 3D should be 13.0, got {}", dist);
}
}
#[test]
fn test_spatial_within_radius() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE poi (id INT, location GEOMETRY)").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (1, POINT(0.0, 0.0))").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (2, POINT(10.0, 10.0))").unwrap();
db.execute("INSERT INTO poi (id, location) VALUES (3, POINT(1.0, 0.0))").unwrap();
let r = rows(&db, "SELECT id FROM poi WHERE WITHIN_RADIUS(location, POINT(0.0, 0.0), 5.0) ORDER BY id");
assert_eq!(r.len(), 2);
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[1][0], Value::Integer(3));
}
#[test]
fn test_fts_insert_and_search() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE docs (id INT, content TEXT)").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (1, 'the quick brown fox jumps over the lazy dog')").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (2, 'a fox is a clever animal')").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (3, 'the dog barks at night')").unwrap();
let r = rows(&db, "SELECT id FROM docs WHERE content LIKE '%fox%' ORDER BY id");
assert_eq!(r.len(), 2);
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[1][0], Value::Integer(2));
}
#[test]
fn test_fts_case_insensitive_like() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE docs (id INT, content TEXT)").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (1, 'Hello World')").unwrap();
let r = rows(&db, "SELECT id FROM docs WHERE content LIKE '%hello%'");
assert!(r.len() <= 1); }
#[test]
fn test_fts_match_query() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE docs (id INT, content TEXT)").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (1, 'machine learning algorithms')").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (2, 'deep learning neural networks')").unwrap();
db.execute("INSERT INTO docs (id, content) VALUES (3, 'cooking recipes for dinner')").unwrap();
let r = rows(&db, "SELECT id FROM docs WHERE MATCH(content, 'learning') ORDER BY id");
assert!(r.len() >= 2, "Should find at least 2 docs with 'learning', got {}", r.len());
}
#[test]
fn test_timestamp_insert_and_select() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE events (id INT, ts TIMESTAMP)").unwrap();
db.execute("INSERT INTO events (id, ts) VALUES (1, 1700000000000000)").unwrap();
db.execute("INSERT INTO events (id, ts) VALUES (2, 1700000050000000)").unwrap();
let r = rows(&db, "SELECT id FROM events ORDER BY id");
assert_eq!(r.len(), 2);
}
#[test]
fn test_timestamp_comparison() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE events (id INT, ts TIMESTAMP)").unwrap();
db.execute("INSERT INTO events (id, ts) VALUES (1, 1700000000000000)").unwrap();
db.execute("INSERT INTO events (id, ts) VALUES (2, 1700000050000000)").unwrap();
db.execute("INSERT INTO events (id, ts) VALUES (3, 1700000100000000)").unwrap();
let r = rows(&db, "SELECT id FROM events WHERE ts > 1700000050000000");
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Integer(3));
}
#[test]
fn test_timestamp_functions() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT)").unwrap();
db.execute("INSERT INTO t (id) VALUES (1)").unwrap();
let r = rows(&db, "SELECT HOUR(1700000000000000) AS h FROM t");
if let Value::Integer(h) = r[0][0] {
assert!(h >= 0 && h <= 23, "HOUR should be 0-23, got {}", h);
}
}
#[test]
fn test_mixed_types_in_table() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE mixed (id INT, name TEXT, score FLOAT, active BOOL, embedding VECTOR(2))").unwrap();
db.execute("INSERT INTO mixed (id, name, score, active, embedding) VALUES (1, 'Alice', 95.5, true, [1.0, 0.0])").unwrap();
db.execute("INSERT INTO mixed (id, name, score, active, embedding) VALUES (2, 'Bob', 82.0, false, [0.0, 1.0])").unwrap();
let r = rows(&db, "SELECT id, name, score FROM mixed WHERE active = true");
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Integer(1));
}
#[test]
fn test_null_in_vector_column() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT, emb VECTOR(2))").unwrap();
db.execute("INSERT INTO t (id, emb) VALUES (1, [1.0, 0.0])").unwrap();
db.execute("INSERT INTO t (id) VALUES (2)").unwrap();
let r = rows(&db, "SELECT id FROM t WHERE emb IS NULL");
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Integer(2));
let r = rows(&db, "SELECT id FROM t WHERE emb IS NOT NULL");
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Integer(1));
}
#[test]
fn test_multiple_vector_columns() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT, emb_a VECTOR(2), emb_b VECTOR(2))").unwrap();
db.execute("INSERT INTO t (id, emb_a, emb_b) VALUES (1, [1.0, 0.0], [0.0, 1.0])").unwrap();
db.execute("INSERT INTO t (id, emb_a, emb_b) VALUES (2, [0.0, 1.0], [1.0, 0.0])").unwrap();
let r = rows(&db, "SELECT id FROM t ORDER BY id");
assert_eq!(r.len(), 2);
}