use crate::engine_rules::AggregateParams;
use crate::types::{AggregateExpr, ColumnInfo, SqlDataType, SqlExpr};
const NATIVE_FUNCTIONS: &[&str] = &["count", "sum", "avg", "min", "max", "ts_stddev"];
pub(super) struct NativeShape {
pub group_columns: Vec<String>,
pub bucket_interval_ms: i64,
}
pub(super) fn native_shape(p: &AggregateParams) -> Option<NativeShape> {
if p.grouping_sets.is_some() || p.input_joins || p.hidden_aggregates {
return None;
}
let mut group_columns = Vec::with_capacity(p.group_by.len());
let mut bucket_interval_ms = 0;
for (index, key) in p.group_by.iter().enumerate() {
match key {
SqlExpr::Column { name, .. } => group_columns.push(name.clone()),
SqlExpr::Function { .. }
| SqlExpr::BinaryOp { .. }
| SqlExpr::UnaryOp { .. }
| SqlExpr::Cast { .. }
| SqlExpr::IsNull { .. }
| SqlExpr::Case { .. }
| SqlExpr::InList { .. }
| SqlExpr::Between { .. }
| SqlExpr::Like { .. }
| SqlExpr::ArrayLiteral(_)
| SqlExpr::Literal(_)
| SqlExpr::Subquery(_)
| SqlExpr::Wildcard => match p.time_bucket {
Some(tb) if tb.group_index == index && tb.interval_ms > 0 => {
bucket_interval_ms = tb.interval_ms;
}
_ => return None,
},
}
}
if !p.having.is_empty() && bucket_interval_ms > 0 {
return None;
}
p.aggregates
.iter()
.all(|a| native_aggregate(a, &p.columns))
.then_some(NativeShape {
group_columns,
bucket_interval_ms,
})
}
fn native_aggregate(a: &AggregateExpr, columns: &[ColumnInfo]) -> bool {
if a.distinct
|| a.grouping_col_index.is_some()
|| a.filter_text.is_some()
|| !NATIVE_FUNCTIONS.contains(&a.function.as_str())
{
return false;
}
match a.args.as_slice() {
[] | [SqlExpr::Wildcard] => a.function == "count",
[SqlExpr::Column { name, .. }] => {
columns.iter().find(|c| &c.name == name).is_some_and(|c| {
a.function == "count"
|| matches!(
c.data_type,
SqlDataType::Int64
| SqlDataType::Float64
| SqlDataType::Timestamp
| SqlDataType::Timestamptz
)
})
}
_ => false,
}
}