use crate::expression::SelectedValue;
use crate::predicate::{FilterExpression, FilterExpressionInner, PredicateComparison};
use crate::{FieldIndex, QueryError, QueryErrorInner};
use csv::StringRecord;
use syn::{Ident, LitStr};
pub struct RecordOps<'a> {
record: &'a StringRecord,
}
impl<'a> RecordOps<'a> {
pub fn new(record: &'a StringRecord) -> Self {
Self { record }
}
pub fn get_field(&self, field_index: &FieldIndex) -> Result<&str, QueryError> {
match field_index {
FieldIndex::Index(idx) => self.record.get(*idx).ok_or_else(|| {
Box::new(QueryErrorInner::InvalidSource {
file: LitStr::new("unknown", proc_macro2::Span::call_site()),
message: format!(
"Not enough values on line {:?}",
self.record.position().map(|p| p.line())
),
})
}),
FieldIndex::PivotKey => {
panic!("PivotKey field access should be handled in pivot context")
}
FieldIndex::PivotValue => {
panic!("PivotValue field access should be handled in pivot context")
}
}
}
pub fn get_selected_value(
&self,
field_index: &FieldIndex,
) -> Result<SelectedValue, QueryError> {
self.get_field(field_index)
.map(|value| SelectedValue(value.to_string()))
}
pub fn matches_predicate(
&self,
predicate: &FilterExpression,
field_resolver: &impl FieldResolver,
) -> bool {
self.matches_predicate_inner(&predicate.expr, field_resolver)
}
fn matches_predicate_inner(
&self,
predicate: &FilterExpressionInner,
field_resolver: &impl FieldResolver,
) -> bool {
match predicate {
FilterExpressionInner::Comparison(comparison) => {
self.matches_comparison(comparison, field_resolver)
}
FilterExpressionInner::All(all) => all
.iter()
.all(|pred| self.matches_predicate_inner(pred, field_resolver)),
FilterExpressionInner::Any(any) => any
.iter()
.any(|pred| self.matches_predicate_inner(pred, field_resolver)),
}
}
fn matches_comparison(
&self,
comparison: &PredicateComparison,
field_resolver: &impl FieldResolver,
) -> bool {
let field_index = match field_resolver.get_field_index(&comparison.field) {
Ok(idx) => idx,
Err(_) => return false,
};
match field_index {
FieldIndex::Index(idx) => {
if let Some(record_value) = self.record.get(idx) {
let record_value =
syn::parse_str::<syn::Lit>(record_value).unwrap_or_else(|_| {
LitStr::new(record_value, comparison.field.span()).into()
});
comparison.evaluate(record_value)
} else {
false
}
}
FieldIndex::PivotKey => {
true
}
FieldIndex::PivotValue => {
false
}
}
}
}
pub trait FieldResolver {
fn get_field_index(&self, field_ident: &Ident) -> Result<FieldIndex, QueryError>;
}