use crate::db::{
executor::ScalarNumericFieldBoundaryRequest,
query::{
builder::aggregate::AggregateExplain,
plan::{AggregateKind, FieldSlot},
},
};
#[cfg(test)]
use crate::db::query::builder::aggregate::{AggregateExpr, avg, sum};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum NumericFieldRequest {
Sum,
SumDistinct,
Avg,
AvgDistinct,
}
impl NumericFieldRequest {
#[expect(
clippy::unnecessary_wraps,
reason = "all request enums expose the same optional aggregate-kind shape"
)]
const fn aggregate_kind(self) -> Option<AggregateKind> {
match self {
Self::Sum | Self::SumDistinct => Some(AggregateKind::Sum),
Self::Avg | Self::AvgDistinct => Some(AggregateKind::Avg),
}
}
const fn is_distinct(self) -> bool {
matches!(self, Self::SumDistinct | Self::AvgDistinct)
}
fn boundary_request(self) -> ScalarNumericFieldBoundaryRequest {
match (self.aggregate_kind(), self.is_distinct()) {
(Some(AggregateKind::Sum), false) => ScalarNumericFieldBoundaryRequest::Sum,
(Some(AggregateKind::Sum), true) => ScalarNumericFieldBoundaryRequest::SumDistinct,
(Some(AggregateKind::Avg), false) => ScalarNumericFieldBoundaryRequest::Avg,
(Some(AggregateKind::Avg), true) => ScalarNumericFieldBoundaryRequest::AvgDistinct,
(
Some(
AggregateKind::Count
| AggregateKind::Exists
| AggregateKind::Min
| AggregateKind::Max
| AggregateKind::First
| AggregateKind::Last,
)
| None,
_,
) => unreachable!("numeric field requests only project SUM/AVG"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct NumericFieldStrategy {
target_field: FieldSlot,
request: NumericFieldRequest,
}
impl NumericFieldStrategy {
#[must_use]
pub(crate) const fn sum_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
request: NumericFieldRequest::Sum,
}
}
#[must_use]
pub(crate) const fn sum_distinct_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
request: NumericFieldRequest::SumDistinct,
}
}
#[must_use]
pub(crate) const fn avg_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
request: NumericFieldRequest::Avg,
}
}
#[must_use]
pub(crate) const fn avg_distinct_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
request: NumericFieldRequest::AvgDistinct,
}
}
#[cfg(test)]
#[must_use]
pub(crate) fn aggregate(&self) -> AggregateExpr {
let field = self.target_field.field();
let aggregate = match self.request.aggregate_kind() {
Some(AggregateKind::Sum) => sum(field),
Some(AggregateKind::Avg) => avg(field),
Some(
AggregateKind::Count
| AggregateKind::Exists
| AggregateKind::Min
| AggregateKind::Max
| AggregateKind::First
| AggregateKind::Last,
)
| None => {
unreachable!("numeric field strategy only projects SUM/AVG aggregate expressions")
}
};
if self.request.is_distinct() {
aggregate.distinct()
} else {
aggregate
}
}
#[cfg(test)]
#[must_use]
pub(crate) const fn aggregate_kind(&self) -> AggregateKind {
match self.request.aggregate_kind() {
Some(kind) => kind,
None => unreachable!(),
}
}
#[cfg(test)]
#[must_use]
pub(crate) fn projected_field(&self) -> &str {
self.target_field.field()
}
#[cfg(test)]
#[must_use]
pub(crate) const fn target_field(&self) -> &FieldSlot {
&self.target_field
}
#[cfg(test)]
#[must_use]
pub(crate) const fn request(&self) -> NumericFieldRequest {
self.request
}
#[must_use]
pub(in crate::db) fn into_executor_request(
self,
) -> (FieldSlot, ScalarNumericFieldBoundaryRequest) {
(self.target_field, self.request.boundary_request())
}
}
impl AggregateExplain for NumericFieldStrategy {
fn explain_aggregate_kind(&self) -> Option<AggregateKind> {
self.request.aggregate_kind()
}
fn explain_projected_field(&self) -> Option<&str> {
Some(self.target_field.field())
}
}