use velesdb_core::{Database, Point};
use super::helpers::{create_test_db, execute_sql};
fn setup_docs_collection(db: &Database) {
execute_sql(
db,
"CREATE COLLECTION docs (dimension = 4, metric = 'cosine')",
)
.expect("test: create docs");
let vc = db.get_vector_collection("docs").expect("test: get docs");
vc.upsert(vec![
Point::new(
1,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "web", "title": "Alpha", "score": 100.0})),
),
Point::new(
2,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "web", "title": "Bravo", "score": 90.0})),
),
Point::new(
3,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "api", "title": "Alpha", "score": 90.0})),
),
Point::new(
4,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "api", "title": "Charlie", "score": 80.0})),
),
Point::new(
5,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "kb", "title": "Alpha", "score": 80.0})),
),
Point::new(
6,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "kb", "title": "Bravo", "score": 70.0})),
),
])
.expect("test: upsert docs");
}
fn payload_u64(result: &velesdb_core::SearchResult, field: &str) -> Option<u64> {
result
.point
.payload
.as_ref()
.and_then(|p| p.get(field))
.and_then(serde_json::Value::as_u64)
}
#[test]
fn test_row_number_over_order_by_score_desc() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT id, score, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn FROM docs LIMIT 10",
)
.expect("test: query");
assert_eq!(results.len(), 6);
let by_id: std::collections::HashMap<u64, u64> = results
.iter()
.map(|r| (r.point.id, payload_u64(r, "rn").expect("rn present")))
.collect();
assert_eq!(by_id[&1], 1, "score=100 → rn 1");
assert_eq!(by_id[&6], 6, "score=70 → rn 6");
let rns_for_tie_90: Vec<u64> = [2, 3].iter().map(|id| by_id[id]).collect();
assert!(rns_for_tie_90.contains(&2) && rns_for_tie_90.contains(&3));
let rns_for_tie_80: Vec<u64> = [4, 5].iter().map(|id| by_id[id]).collect();
assert!(rns_for_tie_80.contains(&4) && rns_for_tie_80.contains(&5));
}
#[test]
fn test_rank_partition_by_source_order_by_score_desc() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT id, source, score, \
RANK() OVER (PARTITION BY source ORDER BY score DESC) AS rnk \
FROM docs LIMIT 10",
)
.expect("test: query");
assert_eq!(results.len(), 6);
let by_id: std::collections::HashMap<u64, u64> = results
.iter()
.map(|r| (r.point.id, payload_u64(r, "rnk").expect("rnk present")))
.collect();
assert_eq!(by_id[&1], 1);
assert_eq!(by_id[&2], 2);
assert_eq!(by_id[&3], 1);
assert_eq!(by_id[&4], 2);
assert_eq!(by_id[&5], 1);
assert_eq!(by_id[&6], 2);
}
#[test]
fn test_dense_rank_single_partition_with_ties_has_no_gaps() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT id, score, DENSE_RANK() OVER (ORDER BY score DESC) AS drnk FROM docs LIMIT 10",
)
.expect("test: query");
assert_eq!(results.len(), 6);
let by_id: std::collections::HashMap<u64, u64> = results
.iter()
.map(|r| (r.point.id, payload_u64(r, "drnk").expect("drnk present")))
.collect();
assert_eq!(by_id[&1], 1); assert_eq!(by_id[&2], 2); assert_eq!(by_id[&3], 2); assert_eq!(by_id[&4], 3); assert_eq!(by_id[&5], 3); assert_eq!(by_id[&6], 4); }
#[test]
fn test_distinct_then_window_numbers_survivors_contiguously() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT DISTINCT title, ROW_NUMBER() OVER (ORDER BY title ASC) AS rn \
FROM docs LIMIT 10",
)
.expect("test: query");
assert_eq!(
results.len(),
3,
"DISTINCT collapses 6 rows down to 3 unique titles"
);
let mut rns: Vec<u64> = results
.iter()
.map(|r| payload_u64(r, "rn").expect("rn present"))
.collect();
rns.sort_unstable();
assert_eq!(
rns,
vec![1, 2, 3],
"window function numbers the 3 survivors 1..3, no gaps"
);
}
#[test]
fn test_distinct_with_qualified_wildcard_dedupes_by_full_payload() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT DISTINCT docs.*, title FROM docs WHERE title = 'Alpha' LIMIT 10",
)
.expect("test: query");
assert_eq!(
results.len(),
3,
"rows differ on source + score (wildcard-expanded) so all three survive"
);
let mut sources: Vec<String> = results
.iter()
.map(|r| {
r.point
.payload
.as_ref()
.and_then(|p| p.get("source"))
.and_then(serde_json::Value::as_str)
.expect("source present")
.to_string()
})
.collect();
sources.sort();
assert_eq!(sources, vec!["api", "kb", "web"]);
}
#[test]
fn test_rank_alias_collides_with_payload_score_end_to_end() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT id, RANK() OVER (ORDER BY score DESC) AS score FROM docs LIMIT 10",
)
.expect("test: query");
assert_eq!(results.len(), 6);
let ranks_by_id: std::collections::HashMap<u64, u64> = results
.iter()
.map(|r| (r.point.id, payload_u64(r, "score").expect("score present")))
.collect();
assert_eq!(ranks_by_id[&1], 1, "id=1 score=100 rank 1");
assert_eq!(ranks_by_id[&2], 2);
assert_eq!(ranks_by_id[&3], 2);
assert_eq!(ranks_by_id[&4], 4);
assert_eq!(ranks_by_id[&5], 4);
assert_eq!(ranks_by_id[&6], 6);
}
#[test]
fn test_window_function_on_empty_result_returns_no_rows() {
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let results = execute_sql(
&db,
"SELECT id, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn \
FROM docs WHERE score > 99999 LIMIT 10",
)
.expect("test: query");
assert!(results.is_empty(), "filter selects nothing → 0 rows");
let all = execute_sql(
&db,
"SELECT id, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn FROM docs LIMIT 10",
)
.expect("test: query");
assert_eq!(
all.len(),
6,
"all 6 docs survive when no filter excludes them"
);
let mut rns: Vec<u64> = all
.iter()
.map(|r| payload_u64(r, "rn").expect("rn present"))
.collect();
rns.sort_unstable();
assert_eq!(
rns,
vec![1, 2, 3, 4, 5, 6],
"evaluator injects a contiguous 1..6 ROW_NUMBER on non-empty input"
);
}
#[test]
fn test_window_function_coexists_with_vector_search_near() {
use super::helpers::{execute_sql_with_params, vector_param};
let (_dir, db) = create_test_db();
setup_docs_collection(&db);
let params = vector_param(&[1.0, 0.0, 0.0, 0.0]);
let results = execute_sql_with_params(
&db,
"SELECT id, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn \
FROM docs WHERE vector NEAR $v LIMIT 10",
¶ms,
)
.expect("test: query");
assert_eq!(
results.len(),
6,
"all 6 docs returned by NEAR (k=10 but only 6 exist)"
);
let mut rns: Vec<u64> = results
.iter()
.map(|r| payload_u64(r, "rn").expect("rn present in payload on the NEAR path"))
.collect();
rns.sort_unstable();
assert_eq!(
rns,
vec![1, 2, 3, 4, 5, 6],
"ROW_NUMBER over NEAR-ranked rows emits a contiguous 1..6 permutation, not a constant/garbage"
);
}