use std::borrow::Cow;
use crate::{
db::query::{
builder::AggregateExpr,
plan::{
AggregateKind, FieldSlot, GroupAggregateSpec, GroupHavingClause, GroupHavingExpr,
GroupHavingSymbol, GroupHavingValueExpr, GroupPlan, GroupSpec, GroupedExecutionConfig,
},
},
error::InternalError,
model::{
entity::{EntityModel, resolve_field_slot},
field::FieldKind,
},
value::Value,
};
impl GroupAggregateSpec {
#[must_use]
pub(in crate::db) fn from_aggregate_expr(aggregate: &AggregateExpr) -> Self {
Self {
kind: aggregate.kind(),
target_field: aggregate.target_field().map(str::to_string),
distinct: aggregate.is_distinct(),
}
}
#[must_use]
pub(crate) const fn kind(&self) -> AggregateKind {
self.kind
}
#[must_use]
pub(crate) fn target_field(&self) -> Option<&str> {
self.target_field.as_deref()
}
#[must_use]
pub(crate) const fn distinct(&self) -> bool {
self.distinct
}
#[must_use]
pub(in crate::db) const fn streaming_compatible_v1(&self) -> bool {
match self.kind {
AggregateKind::Count => !self.distinct,
AggregateKind::Sum | AggregateKind::Avg | AggregateKind::Min | AggregateKind::Max => {
!self.distinct && self.target_field.is_some()
}
AggregateKind::Exists | AggregateKind::First | AggregateKind::Last => {
self.target_field.is_none()
&& (!self.distinct || self.kind.supports_grouped_distinct_v1())
}
}
}
}
impl GroupSpec {
#[must_use]
pub(in crate::db) fn global_distinct_shape_from_aggregate_expr(
aggregate: &AggregateExpr,
execution: GroupedExecutionConfig,
) -> Self {
Self {
group_fields: Vec::new(),
aggregates: vec![GroupAggregateSpec::from_aggregate_expr(aggregate)],
execution,
}
}
}
impl GroupPlan {
#[must_use]
pub(in crate::db) fn effective_having_expr(&self) -> Option<Cow<'_, GroupHavingExpr>> {
self.having_expr.as_ref().map(Cow::Borrowed)
}
}
impl GroupHavingExpr {
#[must_use]
pub(in crate::db) fn from_clause(clause: &GroupHavingClause) -> Self {
Self::Compare {
left: GroupHavingValueExpr::from_legacy_symbol(clause.symbol()),
op: clause.op(),
right: GroupHavingValueExpr::Literal(clause.value().clone()),
}
}
#[must_use]
pub(in crate::db) fn and(self, expr: Self) -> Self {
match self {
Self::And(mut children) => {
children.push(expr);
Self::And(children)
}
existing @ Self::Compare { .. } => Self::And(vec![existing, expr]),
}
}
#[cfg(test)]
#[must_use]
pub(in crate::db) fn compare_symbol(
symbol: GroupHavingSymbol,
op: crate::db::predicate::CompareOp,
value: Value,
) -> Self {
Self::Compare {
left: GroupHavingValueExpr::from_legacy_symbol(&symbol),
op,
right: GroupHavingValueExpr::Literal(value),
}
}
}
impl GroupHavingValueExpr {
#[must_use]
pub(in crate::db) fn from_legacy_symbol(symbol: &GroupHavingSymbol) -> Self {
match symbol {
GroupHavingSymbol::GroupField(field_slot) => Self::GroupField(field_slot.clone()),
GroupHavingSymbol::AggregateIndex(index) => Self::AggregateIndex(*index),
}
}
}
pub(in crate::db) fn canonicalize_grouped_having_numeric_literal_for_field_kind(
field_kind: Option<FieldKind>,
value: &Value,
) -> Option<Value> {
match field_kind? {
FieldKind::Relation { key_kind, .. } => {
canonicalize_grouped_having_numeric_literal_for_field_kind(Some(*key_kind), value)
}
FieldKind::Int => match value {
Value::Int(inner) => Some(Value::Int(*inner)),
Value::Uint(inner) => i64::try_from(*inner).ok().map(Value::Int),
_ => None,
},
FieldKind::Uint => match value {
Value::Int(inner) => u64::try_from(*inner).ok().map(Value::Uint),
Value::Uint(inner) => Some(Value::Uint(*inner)),
_ => None,
},
_ => None,
}
}
impl GroupHavingClause {
#[must_use]
pub(crate) const fn symbol(&self) -> &GroupHavingSymbol {
&self.symbol
}
#[must_use]
pub(crate) const fn op(&self) -> crate::db::predicate::CompareOp {
self.op
}
#[must_use]
pub(crate) const fn value(&self) -> &Value {
&self.value
}
pub(in crate::db) fn unsupported_operator(
op: crate::db::predicate::CompareOp,
) -> InternalError {
InternalError::query_executor_invariant(format!(
"unsupported grouped HAVING operator reached executor: {op:?}",
))
}
}
impl GroupHavingSymbol {
pub(in crate::db) fn grouped_key_must_be_list(value: &Value) -> InternalError {
InternalError::query_executor_invariant(format!(
"grouped HAVING requires list-shaped grouped keys, found {value:?}",
))
}
pub(in crate::db) fn field_not_in_group_key_projection(field: &str) -> InternalError {
InternalError::query_executor_invariant(format!(
"grouped HAVING field is not in grouped key projection: field='{field}'",
))
}
pub(in crate::db) fn group_key_offset_out_of_bounds(
clause_index: usize,
offset: usize,
key_len: usize,
) -> InternalError {
InternalError::query_executor_invariant(format!(
"grouped HAVING group key offset out of bounds: clause_index={clause_index}, offset={offset}, key_len={key_len}",
))
}
pub(in crate::db) fn aggregate_index_out_of_bounds(
clause_index: usize,
aggregate_index: usize,
aggregate_count: usize,
) -> InternalError {
InternalError::query_executor_invariant(format!(
"grouped HAVING aggregate index out of bounds: clause_index={clause_index}, aggregate_index={aggregate_index}, aggregate_count={aggregate_count}",
))
}
}
impl FieldSlot {
#[must_use]
pub(crate) fn resolve(model: &EntityModel, field: &str) -> Option<Self> {
let index = resolve_field_slot(model, field)?;
let canonical = model
.fields
.get(index)
.map_or(field, |model_field| model_field.name);
Some(Self {
index,
field: canonical.to_string(),
kind: model.fields.get(index).map(|field| field.kind),
})
}
#[must_use]
pub(crate) const fn index(&self) -> usize {
self.index
}
#[must_use]
pub(crate) fn field(&self) -> &str {
&self.field
}
#[must_use]
pub(crate) const fn kind(&self) -> Option<FieldKind> {
self.kind
}
}