use arrow::datatypes::DECIMAL128_MAX_PRECISION;
use super::{AggregateExpr, AggregateFunction, ArrowDataType, Expr, QueryError, QueryErrorCode, Result};
use crate::expr::{BinaryOp, CaseWhen, LiteralValue};
const DATAFUSION_AVG_SUM_HEADROOM_DIGITS: u8 = 13;
pub const AVG_COUNT_STATE_TYPE: ArrowDataType = ArrowDataType::Int64;
pub(crate) fn avg_declared_sum_state_type(
input_type: &ArrowDataType,
output_type: &ArrowDataType,
) -> Result<ArrowDataType> {
if let ArrowDataType::Decimal128(precision, _) = input_type
&& precision.saturating_add(DATAFUSION_AVG_SUM_HEADROOM_DIGITS) > DECIMAL128_MAX_PRECISION
{
return Err(unsupported_avg_types(input_type, output_type));
}
avg_sum_state_type(input_type, output_type)
}
pub fn avg_sum_state_type(input_type: &ArrowDataType, output_type: &ArrowDataType) -> Result<ArrowDataType> {
match (input_type, output_type) {
(input, ArrowDataType::Float32 | ArrowDataType::Float64) if sums_as_float64(input) => {
Ok(ArrowDataType::Float64)
},
(ArrowDataType::Decimal128(_, input_scale), ArrowDataType::Decimal128(_, output_scale))
if output_scale >= input_scale =>
{
Ok(ArrowDataType::Decimal128(DECIMAL128_MAX_PRECISION, *input_scale))
},
_ => Err(unsupported_avg_types(input_type, output_type)),
}
}
pub fn avg_argument_cast_type(input_type: &ArrowDataType) -> Option<ArrowDataType> {
(sums_as_float64(input_type) && *input_type != ArrowDataType::Float64).then_some(ArrowDataType::Float64)
}
fn sums_as_float64(input_type: &ArrowDataType) -> bool {
input_type.is_integer() || input_type.is_floating()
}
fn unsupported_avg_types(input_type: &ArrowDataType, output_type: &ArrowDataType) -> QueryError {
QueryError::new(
QueryErrorCode::UnsupportedType,
"query_engine.aggregate.avg_state_type",
"avg has no native sum state that reproduces DataFusion for this input and output type",
)
.with_fact("input_type", format!("{input_type:?}"))
.with_fact("output_type", format!("{output_type:?}"))
}
pub(crate) fn avg_division_type(output_type: &ArrowDataType) -> Option<ArrowDataType> {
match output_type {
ArrowDataType::Float32 | ArrowDataType::Float64 => Some(ArrowDataType::Float64),
ArrowDataType::Decimal128(_, scale) => Some(ArrowDataType::Decimal128(DECIMAL128_MAX_PRECISION, *scale)),
_ => None,
}
}
pub(crate) fn avg_partial_aggregates(arg: &Expr, sum_alias: &str, count_alias: &str) -> [AggregateExpr; 2] {
[
AggregateExpr::new(AggregateFunction::Sum, arg.clone(), sum_alias),
AggregateExpr::new(AggregateFunction::Count, arg.clone(), count_alias),
]
}
pub(crate) fn avg_merge_aggregates(sum: &str, count: &str) -> [AggregateExpr; 2] {
[
AggregateExpr::new(AggregateFunction::Sum, Expr::column(sum), sum),
AggregateExpr::new(AggregateFunction::Sum, Expr::column(count), count),
]
}
pub fn avg_final_expr(sum: Expr, count: Expr, output_type: &ArrowDataType) -> Result<Expr> {
let division_type = avg_division_type(output_type).ok_or_else(|| {
QueryError::new(
QueryErrorCode::UnsupportedType,
"query_engine.aggregate.avg_division_type",
"avg output must be Float32, Float64, or Decimal128",
)
.with_fact("output_type", format!("{output_type:?}"))
})?;
let count_is_zero = Expr::binary(count.clone(), BinaryOp::Eq, Expr::literal(LiteralValue::Int64(0)));
let sum = Expr::cast(sum, division_type.clone());
let quotient = match division_type {
ArrowDataType::Decimal128(precision, _) => Expr::binary_with_output_type(
sum,
BinaryOp::Divide,
Expr::cast(count, ArrowDataType::Decimal128(precision, 0)),
division_type,
),
_ => Expr::binary(sum, BinaryOp::Divide, Expr::cast(count, division_type)),
};
Ok(Expr::case(
vec![CaseWhen::new(count_is_zero, Expr::literal(LiteralValue::Null(output_type.clone())))],
Expr::cast(quotient, output_type.clone()),
))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AvgStateAliases {
sum_alias: String,
count_alias: String,
}
impl AvgStateAliases {
#[doc(hidden)]
pub fn for_aggregate_index(index: usize) -> Self {
Self {
sum_alias: format!("__algeon_native_avg_{index}_sum"),
count_alias: format!("__algeon_native_avg_{index}_count"),
}
}
#[doc(hidden)]
pub fn sum(&self) -> &str {
self.sum_alias.as_str()
}
#[doc(hidden)]
pub fn count(&self) -> &str {
self.count_alias.as_str()
}
#[doc(hidden)]
pub fn sum_expr(&self) -> Expr {
Expr::column(self.sum_alias.clone())
}
#[doc(hidden)]
pub fn count_expr(&self) -> Expr {
Expr::column(self.count_alias.clone())
}
#[doc(hidden)]
pub fn output_names(&self) -> [String; 2] {
[self.sum_alias.clone(), self.count_alias.clone()]
}
#[doc(hidden)]
pub fn partial_aggregates(&self, arg: &Expr) -> [AggregateExpr; 2] {
avg_partial_aggregates(arg, self.sum(), self.count())
}
#[doc(hidden)]
pub fn merge_aggregates(&self) -> [AggregateExpr; 2] {
avg_merge_aggregates(self.sum(), self.count())
}
}