use crate::catalog::{Catalog, CatalogColumn, CatalogRelation};
use crate::error::HolocronError;
use crate::query::filter::{Comparison, Filter};
use crate::query::Query;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckedQuery {
query: Query,
}
impl CheckedQuery {
pub fn query(&self) -> &Query {
&self.query
}
}
pub fn check_query(catalog: &Catalog, query: Query) -> Result<CheckedQuery, Vec<HolocronError>> {
let Some(relation) = catalog.relation(&query.relation) else {
return Err(vec![HolocronError::unknown_relation(&query.relation)]);
};
let mut errors = Vec::new();
if let Some(filter) = &query.filter {
check_filter(filter, &query.relation, relation, &mut errors);
}
if errors.is_empty() {
Ok(CheckedQuery { query })
} else {
Err(errors)
}
}
fn check_filter(
filter: &Filter,
relation_name: &str,
relation: &CatalogRelation,
errors: &mut Vec<HolocronError>,
) {
match filter {
Filter::And(children) | Filter::Or(children) => {
for child in children {
check_filter(child, relation_name, relation, errors);
}
}
Filter::Leaf(comparison) => check_comparison(comparison, relation_name, relation, errors),
}
}
fn check_comparison(
comparison: &Comparison,
relation_name: &str,
relation: &CatalogRelation,
errors: &mut Vec<HolocronError>,
) {
match comparison {
Comparison::Compare { column, op, .. } => {
let Some(resolved) = resolve_filterable(relation_name, relation, column, errors) else {
return;
};
if !op.supported_by(&resolved.data_type) {
errors.push(HolocronError::operator_not_supported(
relation_name,
column,
resolved.data_type.name(),
op.name(),
));
}
}
Comparison::Set { column, op, .. } => {
let Some(resolved) = resolve_filterable(relation_name, relation, column, errors) else {
return;
};
if !op.supported_by(&resolved.data_type) {
errors.push(HolocronError::operator_not_supported(
relation_name,
column,
resolved.data_type.name(),
op.name(),
));
}
}
Comparison::NullCheck { column, .. } => {
resolve_filterable(relation_name, relation, column, errors);
}
}
}
fn resolve_filterable<'r>(
relation_name: &str,
relation: &'r CatalogRelation,
column: &str,
errors: &mut Vec<HolocronError>,
) -> Option<&'r CatalogColumn> {
let Some(resolved) = relation.column(column) else {
let candidates = relation
.columns
.iter()
.map(|column| column.name.clone())
.collect();
errors.push(HolocronError::unknown_column(
relation_name,
column,
candidates,
crate::span::Span::default(),
));
return None;
};
if !resolved.filterable {
errors.push(HolocronError::not_filterable(relation_name, column));
return None;
}
Some(resolved)
}