use crate::collection::graph::GraphEdge;
use crate::collection::types::Collection;
use crate::guardrails::{CircuitState, GuardRails, QueryContext, QueryLimits};
use crate::point::Point;
use crate::velesql::{
CompareOp, Comparison, Condition, DistinctMode, Parser, Query, SelectColumns, SelectStatement,
SimilarityCondition, Value, VectorExpr,
};
use std::collections::HashMap;
use std::sync::Arc;
use tempfile::TempDir;
fn create_test_collection(dir: &TempDir) -> Collection {
let path = dir.path().join("test_col");
let col = Collection::create(path, 4, crate::distance::DistanceMetric::Cosine)
.expect("Failed to create test collection");
let points: Vec<Point> = (0u64..10)
.map(|i| {
#[allow(clippy::cast_precision_loss)]
Point::new(
i,
vec![i as f32 / 10.0, 0.1, 0.1, 0.1],
Some(serde_json::json!({ "idx": i })),
)
})
.collect();
col.upsert(points).expect("upsert failed");
col
}
#[test]
fn test_execute_query_pre_check_rate_limit() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
rate_limit_qps: 2,
..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sql = "SELECT * FROM col LIMIT 5;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
assert!(col.execute_query(&query, ¶ms).is_ok());
assert!(col.execute_query(&query, ¶ms).is_ok());
let result = col.execute_query(&query, ¶ms);
assert!(result.is_err(), "Expected rate limit error but got Ok");
let err_str = result.unwrap_err().to_string();
assert!(
err_str.contains("Guard-rail") || err_str.contains("Rate limit"),
"Unexpected error: {err_str}"
);
}
#[test]
fn test_execute_query_cardinality_enforced() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
max_cardinality: 3, ..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sql = "SELECT * FROM col LIMIT 100;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
let result = col.execute_query(&query, ¶ms);
assert!(
result.is_err(),
"Expected cardinality guard-rail error but got Ok"
);
let err_str = result.unwrap_err().to_string();
assert!(
err_str.contains("Guard-rail") || err_str.contains("Cardinality"),
"Unexpected error: {err_str}"
);
}
#[test]
fn test_execute_query_timeout_disabled_at_zero() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
timeout_ms: 0,
..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sql = "SELECT * FROM col LIMIT 10;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
assert!(
col.execute_query(&query, ¶ms).is_ok(),
"timeout_ms=0 should disable the guard-rail, not reject the query"
);
}
#[test]
fn test_query_context_timeout_fires_after_elapsed() {
let limits = QueryLimits {
timeout_ms: 1, ..QueryLimits::default()
};
let ctx = QueryContext::new(limits);
std::thread::sleep(std::time::Duration::from_millis(5));
let result = ctx.check_timeout();
assert!(
result.is_err(),
"check_timeout should return Err after the timeout has elapsed"
);
let err_str = result.unwrap_err().to_string();
assert!(
err_str.contains("timed out"),
"Unexpected error message: {err_str}"
);
}
#[test]
fn test_execute_query_circuit_breaker_opens_after_failures() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
max_cardinality: 1, circuit_failure_threshold: 2,
circuit_recovery_seconds: 60,
..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sql = "SELECT * FROM col LIMIT 10;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
let _ = col.execute_query(&query, ¶ms); let _ = col.execute_query(&query, ¶ms);
let state = col.guard_rails.circuit_breaker.state();
assert_eq!(
state,
CircuitState::Open,
"Circuit breaker should be Open after repeated failures"
);
}
#[test]
fn test_execute_query_normal_query_records_success() {
let dir = TempDir::new().unwrap();
let col = create_test_collection(&dir);
let sql = "SELECT * FROM col LIMIT 5;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
col.execute_query(&query, ¶ms)
.expect("Query should succeed");
assert_eq!(
col.guard_rails.circuit_breaker.state(),
CircuitState::Closed
);
}
#[test]
fn test_not_similarity_query_cardinality_enforced() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
max_cardinality: 3, ..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sim_cond = Condition::Similarity(SimilarityCondition {
field: "vector".to_string(),
vector: VectorExpr::Literal(vec![0.5, 0.1, 0.1, 0.1]),
operator: CompareOp::Gt,
threshold: 0.99, });
let query = Query::new_select(SelectStatement {
distinct: DistinctMode::None,
columns: SelectColumns::All,
from: "col".to_string(),
from_alias: vec![],
joins: Vec::new(),
where_clause: Some(Condition::Not(Box::new(sim_cond))),
order_by: None,
limit: Some(100),
offset: None,
with_clause: None,
group_by: None,
having: None,
fusion_clause: None,
});
let params = HashMap::new();
let result = col.execute_query(&query, ¶ms);
assert!(
result.is_err(),
"Cardinality guard-rail should fire for NOT-similarity queries"
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("Guard-rail") || err.contains("ardinality"),
"Unexpected error: {err}"
);
}
#[test]
fn test_union_query_cardinality_enforced() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
max_cardinality: 3, ..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sim_cond = Condition::Similarity(SimilarityCondition {
field: "vector".to_string(),
vector: VectorExpr::Literal(vec![0.5, 0.1, 0.1, 0.1]),
operator: CompareOp::Gt,
threshold: 0.0, });
let meta_cond = Condition::Comparison(Comparison {
column: "idx".to_string(),
operator: CompareOp::Gte,
value: Value::Integer(0),
});
let query = Query::new_select(SelectStatement {
distinct: DistinctMode::None,
columns: SelectColumns::All,
from: "col".to_string(),
from_alias: vec![],
joins: Vec::new(),
where_clause: Some(Condition::Or(Box::new(sim_cond), Box::new(meta_cond))),
order_by: None,
limit: Some(100),
offset: None,
with_clause: None,
group_by: None,
having: None,
fusion_clause: None,
});
let params = HashMap::new();
let result = col.execute_query(&query, ¶ms);
assert!(
result.is_err(),
"Cardinality guard-rail should fire for union-mode queries"
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("Guard-rail") || err.contains("ardinality"),
"Unexpected error: {err}"
);
}
#[test]
fn test_match_cardinality_enforced_below_100_iterations() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
for target in 1u64..=3 {
let edge = GraphEdge::new(target * 100, 0, target, "LINKS").expect("edge");
col.add_edge(edge).expect("add_edge");
}
let limits = QueryLimits {
max_cardinality: 2,
..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sql = "MATCH (a)-[r]->(b) RETURN b LIMIT 10;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
let result = col.execute_query(&query, ¶ms);
assert!(
result.is_err(),
"Cardinality guard-rail should fire for MATCH with <100 traversal iterations"
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("Guard-rail") || err.contains("ardinality"),
"Unexpected error: {err}"
);
}
#[test]
fn test_per_client_rate_limiting_is_independent() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
rate_limit_qps: 1,
..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let sql = "SELECT * FROM col LIMIT 5;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
assert!(
col.execute_query_with_client(&query, ¶ms, "client_a")
.is_ok(),
"client_a first call should succeed"
);
assert!(
col.execute_query_with_client(&query, ¶ms, "client_a")
.is_err(),
"client_a second call should be rate-limited"
);
assert!(
col.execute_query_with_client(&query, ¶ms, "client_b")
.is_ok(),
"client_b should have an independent bucket unaffected by client_a exhaustion"
);
}
#[test]
fn test_execute_match_depth_limit_enforced() {
let dir = TempDir::new().unwrap();
let mut col = create_test_collection(&dir);
let limits = QueryLimits {
max_depth: 0,
..QueryLimits::default()
};
col.guard_rails = Arc::new(GuardRails::with_limits(limits));
let edge =
crate::collection::graph::GraphEdge::new(1, 0, 1, "LINKS").expect("GraphEdge::new failed");
col.add_edge(edge).expect("add_edge failed");
let sql = "MATCH (a)-[r]->(b) RETURN a LIMIT 5;";
let query = Parser::parse(sql).expect("parse failed");
let params = HashMap::new();
let result = col.execute_query(&query, ¶ms);
let _ = result;
}