use std::collections::HashSet;
use linkrs_core::error::{DBError, DBResult, QueryError};
use linkrs_core::types::expr::contextual::ContextualExpression;
use linkrs_core::types::expr::Expression;
use linkrs_core::types::expr::{walk_expr, ExprValidator, MAX_EXPR_DEPTH};
use linkrs_core::types::DataType;
const MAX_FUNCTION_ARGS: usize = 100;
const MAX_COLLECTION_ELEMENTS: usize = 10_000;
pub fn validate_expression(expr: &ContextualExpression) -> DBResult<()> {
let Some(expr_meta) = expr.expression() else {
return Err(DBError::from(QueryError::invalid_query(
"Expression not found in context".to_string(),
)));
};
let inner = expr_meta.inner();
let mut validator = CompositeValidator::new();
walk_expr(inner, 0, &mut validator)?;
Ok(())
}
struct CompositeValidator;
impl CompositeValidator {
fn new() -> Self {
Self
}
}
impl ExprValidator for CompositeValidator {
fn validate(&mut self, expr: &Expression, depth: usize) -> DBResult<()> {
if depth > MAX_EXPR_DEPTH {
return Err(DBError::from(QueryError::invalid_query(
"expressions are nested too deeply in levels".to_string(),
)));
}
self.check_division_by_zero(expr)?;
self.check_subscript_types(expr, depth)?;
self.check_case_when(expr)?;
self.check_empty_names(expr)?;
self.check_function_args(expr)?;
self.check_collection_limits(expr)?;
self.check_map_duplicate_keys(expr)?;
Ok(())
}
}
impl CompositeValidator {
fn check_division_by_zero(&mut self, expr: &Expression) -> DBResult<()> {
if let Expression::Binary {
op: linkrs_core::BinaryOperator::Divide | linkrs_core::BinaryOperator::Modulo,
right,
..
} = expr
{
if matches!(
right.as_ref(),
Expression::Literal(linkrs_core::Value::Int(0))
| Expression::Literal(linkrs_core::Value::Float(0.0))
) {
return Err(DBError::from(QueryError::invalid_query(
"The divisor cannot be 0".to_string(),
)));
}
}
Ok(())
}
fn check_subscript_types(&mut self, expr: &Expression, _depth: usize) -> DBResult<()> {
if let Expression::Subscript { collection, index } = expr {
let col_type = collection.deduce_type();
let idx_type = index.deduce_type();
match col_type {
DataType::List(_) => {
if idx_type != DataType::Int
&& idx_type != DataType::Empty
&& idx_type != DataType::Unknown
{
return Err(DBError::from(QueryError::invalid_query(format!(
"List subscripts need to be of integer type, but get: {:?}",
idx_type
))));
}
}
DataType::Map(_) => {
if idx_type != DataType::String
&& idx_type != DataType::Empty
&& idx_type != DataType::Unknown
{
return Err(DBError::from(QueryError::invalid_query(format!(
"Mapping keys requires a string type, but gets: {:?}",
idx_type
))));
}
}
DataType::Empty | DataType::Unknown => {}
_ => {
return Err(DBError::from(QueryError::invalid_query(format!(
"Unsupported types for subscript operations: {:?}",
col_type
))));
}
}
}
Ok(())
}
fn check_case_when(&mut self, expr: &Expression) -> DBResult<()> {
if let Expression::Case { conditions, .. } = expr {
if conditions.is_empty() {
return Err(DBError::from(QueryError::invalid_query(
"CASE expressions must have at least one WHEN clause.".to_string(),
)));
}
}
Ok(())
}
fn check_empty_names(&mut self, expr: &Expression) -> DBResult<()> {
match expr {
Expression::Function { name, .. } => {
if name.is_empty() {
return Err(DBError::from(QueryError::invalid_query(
"Function name cannot be null".to_string(),
)));
}
}
Expression::Property { property, .. } if property.is_empty() => {
return Err(DBError::from(QueryError::invalid_query(
"Attribute name cannot be null".to_string(),
)));
}
_ => {}
}
Ok(())
}
fn check_function_args(&mut self, expr: &Expression) -> DBResult<()> {
let (name, arg_count) = match expr {
Expression::Function { name, args } => (name.as_str(), args.len()),
Expression::WindowFunction { name, args, .. } => (name.as_str(), args.len()),
_ => return Ok(()),
};
if arg_count > MAX_FUNCTION_ARGS {
return Err(DBError::from(QueryError::invalid_query(format!(
"The function {:?} has too many arguments: {}",
name, arg_count
))));
}
let registry = crate::executor::expression::functions::global_registry_ref();
if let Some(func) = registry.get_builtin(name) {
let expected = func.arity();
let variadic = func.is_variadic();
if variadic {
if arg_count < expected {
return Err(DBError::from(QueryError::invalid_query(format!(
"Function '{}' expects at least {} arguments, got {}",
name, expected, arg_count
))));
}
} else if arg_count != expected {
return Err(DBError::from(QueryError::invalid_query(format!(
"Function '{}' expects {} arguments, got {}",
name, expected, arg_count
))));
}
} else if let Some(func) = registry.get_custom(name) {
let expected = func.arity;
let variadic = func.is_variadic;
if variadic {
if arg_count < expected {
return Err(DBError::from(QueryError::invalid_query(format!(
"Function '{}' expects at least {} arguments, got {}",
name, expected, arg_count
))));
}
} else if arg_count != expected {
return Err(DBError::from(QueryError::invalid_query(format!(
"Function '{}' expects {} arguments, got {}",
name, expected, arg_count
))));
}
}
Ok(())
}
fn check_collection_limits(&mut self, expr: &Expression) -> DBResult<()> {
match expr {
Expression::List(items) => {
if items.len() > MAX_COLLECTION_ELEMENTS {
return Err(DBError::from(QueryError::invalid_query(
"Too many list expression elements".to_string(),
)));
}
}
Expression::Map(pairs) if pairs.len() > MAX_COLLECTION_ELEMENTS => {
return Err(DBError::from(QueryError::invalid_query(
"Mapping expressions with too many key-value pairs".to_string(),
)));
}
_ => {}
}
Ok(())
}
fn check_map_duplicate_keys(&mut self, expr: &Expression) -> DBResult<()> {
if let Expression::Map(pairs) = expr {
let mut keys = HashSet::new();
for (key, _) in pairs {
if !keys.insert(key) {
return Err(DBError::from(QueryError::invalid_query(format!(
"There are duplicate keys in the mapping expression: {:?}",
key
))));
}
}
}
Ok(())
}
}