use crate::db::query::plan::{AggregateKind, FieldSlot};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AggregateExpr {
kind: AggregateKind,
target_field: Option<String>,
distinct: bool,
}
impl AggregateExpr {
const fn new(kind: AggregateKind, target_field: Option<String>) -> Self {
Self {
kind,
target_field,
distinct: false,
}
}
#[must_use]
pub const fn distinct(mut self) -> Self {
self.distinct = true;
self
}
#[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 is_distinct(&self) -> bool {
self.distinct
}
pub(in crate::db::query) const fn from_semantic_parts(
kind: AggregateKind,
target_field: Option<String>,
distinct: bool,
) -> Self {
Self {
kind,
target_field,
distinct,
}
}
#[must_use]
pub(in crate::db) fn terminal_for_kind(kind: AggregateKind) -> Self {
match kind {
AggregateKind::Count => count(),
AggregateKind::Exists => exists(),
AggregateKind::Min => min(),
AggregateKind::Max => max(),
AggregateKind::First => first(),
AggregateKind::Last => last(),
AggregateKind::Sum | AggregateKind::Avg => unreachable!(
"AggregateExpr::terminal_for_kind does not support SUM/AVG field-target kinds"
),
}
}
#[must_use]
pub(in crate::db) fn field_target_extrema_for_kind(
kind: AggregateKind,
field: impl AsRef<str>,
) -> Self {
match kind {
AggregateKind::Min => min_by(field),
AggregateKind::Max => max_by(field),
_ => unreachable!("AggregateExpr::field_target_extrema_for_kind requires MIN/MAX kind"),
}
}
}
pub(crate) trait PreparedFluentAggregateExplainStrategy {
fn explain_aggregate_kind(&self) -> Option<AggregateKind>;
fn explain_projected_field(&self) -> Option<&str> {
None
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum PreparedFluentExistingRowsTerminalRuntimeRequest {
CountRows,
ExistsRows,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PreparedFluentExistingRowsTerminalStrategy {
runtime_request: PreparedFluentExistingRowsTerminalRuntimeRequest,
}
impl PreparedFluentExistingRowsTerminalStrategy {
#[must_use]
pub(crate) const fn count_rows() -> Self {
Self {
runtime_request: PreparedFluentExistingRowsTerminalRuntimeRequest::CountRows,
}
}
#[must_use]
pub(crate) const fn exists_rows() -> Self {
Self {
runtime_request: PreparedFluentExistingRowsTerminalRuntimeRequest::ExistsRows,
}
}
#[cfg(test)]
#[must_use]
pub(crate) const fn aggregate(&self) -> AggregateExpr {
match self.runtime_request {
PreparedFluentExistingRowsTerminalRuntimeRequest::CountRows => count(),
PreparedFluentExistingRowsTerminalRuntimeRequest::ExistsRows => exists(),
}
}
#[cfg(test)]
#[must_use]
pub(crate) const fn runtime_request(
&self,
) -> &PreparedFluentExistingRowsTerminalRuntimeRequest {
&self.runtime_request
}
#[must_use]
pub(crate) const fn into_runtime_request(
self,
) -> PreparedFluentExistingRowsTerminalRuntimeRequest {
self.runtime_request
}
}
impl PreparedFluentAggregateExplainStrategy for PreparedFluentExistingRowsTerminalStrategy {
fn explain_aggregate_kind(&self) -> Option<AggregateKind> {
Some(match self.runtime_request {
PreparedFluentExistingRowsTerminalRuntimeRequest::CountRows => AggregateKind::Count,
PreparedFluentExistingRowsTerminalRuntimeRequest::ExistsRows => AggregateKind::Exists,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum PreparedFluentScalarTerminalRuntimeRequest {
IdTerminal {
kind: AggregateKind,
},
IdBySlot {
kind: AggregateKind,
target_field: FieldSlot,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PreparedFluentScalarTerminalStrategy {
runtime_request: PreparedFluentScalarTerminalRuntimeRequest,
}
impl PreparedFluentScalarTerminalStrategy {
#[must_use]
pub(crate) const fn id_terminal(kind: AggregateKind) -> Self {
Self {
runtime_request: PreparedFluentScalarTerminalRuntimeRequest::IdTerminal { kind },
}
}
#[must_use]
pub(crate) const fn id_by_slot(kind: AggregateKind, target_field: FieldSlot) -> Self {
Self {
runtime_request: PreparedFluentScalarTerminalRuntimeRequest::IdBySlot {
kind,
target_field,
},
}
}
#[must_use]
pub(crate) fn into_runtime_request(self) -> PreparedFluentScalarTerminalRuntimeRequest {
self.runtime_request
}
}
impl PreparedFluentAggregateExplainStrategy for PreparedFluentScalarTerminalStrategy {
fn explain_aggregate_kind(&self) -> Option<AggregateKind> {
Some(match self.runtime_request {
PreparedFluentScalarTerminalRuntimeRequest::IdTerminal { kind }
| PreparedFluentScalarTerminalRuntimeRequest::IdBySlot { kind, .. } => kind,
})
}
fn explain_projected_field(&self) -> Option<&str> {
match &self.runtime_request {
PreparedFluentScalarTerminalRuntimeRequest::IdTerminal { .. } => None,
PreparedFluentScalarTerminalRuntimeRequest::IdBySlot { target_field, .. } => {
Some(target_field.field())
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PreparedFluentNumericFieldRuntimeRequest {
Sum,
SumDistinct,
Avg,
AvgDistinct,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PreparedFluentNumericFieldStrategy {
target_field: FieldSlot,
runtime_request: PreparedFluentNumericFieldRuntimeRequest,
}
impl PreparedFluentNumericFieldStrategy {
#[must_use]
pub(crate) const fn sum_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentNumericFieldRuntimeRequest::Sum,
}
}
#[must_use]
pub(crate) const fn sum_distinct_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentNumericFieldRuntimeRequest::SumDistinct,
}
}
#[must_use]
pub(crate) const fn avg_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentNumericFieldRuntimeRequest::Avg,
}
}
#[must_use]
pub(crate) const fn avg_distinct_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentNumericFieldRuntimeRequest::AvgDistinct,
}
}
#[cfg(test)]
#[must_use]
pub(crate) fn aggregate(&self) -> AggregateExpr {
let field = self.target_field.field();
match self.runtime_request {
PreparedFluentNumericFieldRuntimeRequest::Sum => sum(field),
PreparedFluentNumericFieldRuntimeRequest::SumDistinct => sum(field).distinct(),
PreparedFluentNumericFieldRuntimeRequest::Avg => avg(field),
PreparedFluentNumericFieldRuntimeRequest::AvgDistinct => avg(field).distinct(),
}
}
#[cfg(test)]
#[must_use]
pub(crate) const fn aggregate_kind(&self) -> AggregateKind {
match self.runtime_request {
PreparedFluentNumericFieldRuntimeRequest::Sum
| PreparedFluentNumericFieldRuntimeRequest::SumDistinct => AggregateKind::Sum,
PreparedFluentNumericFieldRuntimeRequest::Avg
| PreparedFluentNumericFieldRuntimeRequest::AvgDistinct => AggregateKind::Avg,
}
}
#[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 runtime_request(&self) -> PreparedFluentNumericFieldRuntimeRequest {
self.runtime_request
}
#[must_use]
pub(crate) fn into_runtime_parts(
self,
) -> (FieldSlot, PreparedFluentNumericFieldRuntimeRequest) {
(self.target_field, self.runtime_request)
}
}
impl PreparedFluentAggregateExplainStrategy for PreparedFluentNumericFieldStrategy {
fn explain_aggregate_kind(&self) -> Option<AggregateKind> {
Some(match self.runtime_request {
PreparedFluentNumericFieldRuntimeRequest::Sum
| PreparedFluentNumericFieldRuntimeRequest::SumDistinct => AggregateKind::Sum,
PreparedFluentNumericFieldRuntimeRequest::Avg
| PreparedFluentNumericFieldRuntimeRequest::AvgDistinct => AggregateKind::Avg,
})
}
fn explain_projected_field(&self) -> Option<&str> {
Some(self.target_field.field())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum PreparedFluentOrderSensitiveTerminalRuntimeRequest {
ResponseOrder { kind: AggregateKind },
NthBySlot { target_field: FieldSlot, nth: usize },
MedianBySlot { target_field: FieldSlot },
MinMaxBySlot { target_field: FieldSlot },
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PreparedFluentOrderSensitiveTerminalStrategy {
runtime_request: PreparedFluentOrderSensitiveTerminalRuntimeRequest,
}
impl PreparedFluentOrderSensitiveTerminalStrategy {
#[must_use]
pub(crate) const fn first() -> Self {
Self {
runtime_request: PreparedFluentOrderSensitiveTerminalRuntimeRequest::ResponseOrder {
kind: AggregateKind::First,
},
}
}
#[must_use]
pub(crate) const fn last() -> Self {
Self {
runtime_request: PreparedFluentOrderSensitiveTerminalRuntimeRequest::ResponseOrder {
kind: AggregateKind::Last,
},
}
}
#[must_use]
pub(crate) const fn nth_by_slot(target_field: FieldSlot, nth: usize) -> Self {
Self {
runtime_request: PreparedFluentOrderSensitiveTerminalRuntimeRequest::NthBySlot {
target_field,
nth,
},
}
}
#[must_use]
pub(crate) const fn median_by_slot(target_field: FieldSlot) -> Self {
Self {
runtime_request: PreparedFluentOrderSensitiveTerminalRuntimeRequest::MedianBySlot {
target_field,
},
}
}
#[must_use]
pub(crate) const fn min_max_by_slot(target_field: FieldSlot) -> Self {
Self {
runtime_request: PreparedFluentOrderSensitiveTerminalRuntimeRequest::MinMaxBySlot {
target_field,
},
}
}
#[cfg(test)]
#[must_use]
pub(crate) fn explain_aggregate(&self) -> Option<AggregateExpr> {
match self.runtime_request {
PreparedFluentOrderSensitiveTerminalRuntimeRequest::ResponseOrder { kind } => {
Some(AggregateExpr::terminal_for_kind(kind))
}
PreparedFluentOrderSensitiveTerminalRuntimeRequest::NthBySlot { .. }
| PreparedFluentOrderSensitiveTerminalRuntimeRequest::MedianBySlot { .. }
| PreparedFluentOrderSensitiveTerminalRuntimeRequest::MinMaxBySlot { .. } => None,
}
}
#[cfg(test)]
#[must_use]
pub(crate) const fn runtime_request(
&self,
) -> &PreparedFluentOrderSensitiveTerminalRuntimeRequest {
&self.runtime_request
}
#[must_use]
pub(crate) fn into_runtime_request(self) -> PreparedFluentOrderSensitiveTerminalRuntimeRequest {
self.runtime_request
}
}
impl PreparedFluentAggregateExplainStrategy for PreparedFluentOrderSensitiveTerminalStrategy {
fn explain_aggregate_kind(&self) -> Option<AggregateKind> {
match self.runtime_request {
PreparedFluentOrderSensitiveTerminalRuntimeRequest::ResponseOrder { kind } => {
Some(kind)
}
PreparedFluentOrderSensitiveTerminalRuntimeRequest::NthBySlot { .. }
| PreparedFluentOrderSensitiveTerminalRuntimeRequest::MedianBySlot { .. }
| PreparedFluentOrderSensitiveTerminalRuntimeRequest::MinMaxBySlot { .. } => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PreparedFluentProjectionRuntimeRequest {
Values,
DistinctValues,
CountDistinct,
ValuesWithIds,
TerminalValue { terminal_kind: AggregateKind },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct PreparedFluentProjectionExplainDescriptor<'a> {
terminal: &'static str,
field: &'a str,
output: &'static str,
}
impl<'a> PreparedFluentProjectionExplainDescriptor<'a> {
#[must_use]
pub(crate) const fn terminal_label(self) -> &'static str {
self.terminal
}
#[must_use]
pub(crate) const fn field_label(self) -> &'a str {
self.field
}
#[must_use]
pub(crate) const fn output_label(self) -> &'static str {
self.output
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PreparedFluentProjectionStrategy {
target_field: FieldSlot,
runtime_request: PreparedFluentProjectionRuntimeRequest,
}
impl PreparedFluentProjectionStrategy {
#[must_use]
pub(crate) const fn values_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentProjectionRuntimeRequest::Values,
}
}
#[must_use]
pub(crate) const fn distinct_values_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentProjectionRuntimeRequest::DistinctValues,
}
}
#[must_use]
pub(crate) const fn count_distinct_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentProjectionRuntimeRequest::CountDistinct,
}
}
#[must_use]
pub(crate) const fn values_by_with_ids_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentProjectionRuntimeRequest::ValuesWithIds,
}
}
#[must_use]
pub(crate) const fn first_value_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentProjectionRuntimeRequest::TerminalValue {
terminal_kind: AggregateKind::First,
},
}
}
#[must_use]
pub(crate) const fn last_value_by_slot(target_field: FieldSlot) -> Self {
Self {
target_field,
runtime_request: PreparedFluentProjectionRuntimeRequest::TerminalValue {
terminal_kind: AggregateKind::Last,
},
}
}
#[cfg(test)]
#[must_use]
pub(crate) const fn target_field(&self) -> &FieldSlot {
&self.target_field
}
#[cfg(test)]
#[must_use]
pub(crate) const fn runtime_request(&self) -> PreparedFluentProjectionRuntimeRequest {
self.runtime_request
}
#[must_use]
pub(crate) fn into_runtime_parts(self) -> (FieldSlot, PreparedFluentProjectionRuntimeRequest) {
(self.target_field, self.runtime_request)
}
#[must_use]
pub(crate) fn explain_descriptor(&self) -> PreparedFluentProjectionExplainDescriptor<'_> {
let terminal_label = match self.runtime_request {
PreparedFluentProjectionRuntimeRequest::Values => "values_by",
PreparedFluentProjectionRuntimeRequest::DistinctValues => "distinct_values_by",
PreparedFluentProjectionRuntimeRequest::CountDistinct => "count_distinct_by",
PreparedFluentProjectionRuntimeRequest::ValuesWithIds => "values_by_with_ids",
PreparedFluentProjectionRuntimeRequest::TerminalValue {
terminal_kind: AggregateKind::First,
} => "first_value_by",
PreparedFluentProjectionRuntimeRequest::TerminalValue {
terminal_kind: AggregateKind::Last,
} => "last_value_by",
PreparedFluentProjectionRuntimeRequest::TerminalValue { .. } => {
unreachable!("projection terminal value explain requires FIRST/LAST kind")
}
};
let output_label = match self.runtime_request {
PreparedFluentProjectionRuntimeRequest::Values
| PreparedFluentProjectionRuntimeRequest::DistinctValues => "values",
PreparedFluentProjectionRuntimeRequest::CountDistinct => "count",
PreparedFluentProjectionRuntimeRequest::ValuesWithIds => "values_with_ids",
PreparedFluentProjectionRuntimeRequest::TerminalValue { .. } => "terminal_value",
};
PreparedFluentProjectionExplainDescriptor {
terminal: terminal_label,
field: self.target_field.field(),
output: output_label,
}
}
}
#[must_use]
pub const fn count() -> AggregateExpr {
AggregateExpr::new(AggregateKind::Count, None)
}
#[must_use]
pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
AggregateExpr::new(AggregateKind::Count, Some(field.as_ref().to_string()))
}
#[must_use]
pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
AggregateExpr::new(AggregateKind::Sum, Some(field.as_ref().to_string()))
}
#[must_use]
pub fn avg(field: impl AsRef<str>) -> AggregateExpr {
AggregateExpr::new(AggregateKind::Avg, Some(field.as_ref().to_string()))
}
#[must_use]
pub const fn exists() -> AggregateExpr {
AggregateExpr::new(AggregateKind::Exists, None)
}
#[must_use]
pub const fn first() -> AggregateExpr {
AggregateExpr::new(AggregateKind::First, None)
}
#[must_use]
pub const fn last() -> AggregateExpr {
AggregateExpr::new(AggregateKind::Last, None)
}
#[must_use]
pub const fn min() -> AggregateExpr {
AggregateExpr::new(AggregateKind::Min, None)
}
#[must_use]
pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
AggregateExpr::new(AggregateKind::Min, Some(field.as_ref().to_string()))
}
#[must_use]
pub const fn max() -> AggregateExpr {
AggregateExpr::new(AggregateKind::Max, None)
}
#[must_use]
pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
AggregateExpr::new(AggregateKind::Max, Some(field.as_ref().to_string()))
}
#[cfg(test)]
mod tests {
use crate::db::query::{
builder::{
PreparedFluentExistingRowsTerminalRuntimeRequest,
PreparedFluentExistingRowsTerminalStrategy, PreparedFluentNumericFieldRuntimeRequest,
PreparedFluentNumericFieldStrategy, PreparedFluentOrderSensitiveTerminalRuntimeRequest,
PreparedFluentOrderSensitiveTerminalStrategy, PreparedFluentProjectionRuntimeRequest,
PreparedFluentProjectionStrategy,
},
plan::{AggregateKind, FieldSlot},
};
#[test]
fn prepared_fluent_numeric_field_strategy_sum_distinct_preserves_runtime_shape() {
let rank_slot = FieldSlot::from_parts_for_test(7, "rank");
let strategy = PreparedFluentNumericFieldStrategy::sum_distinct_by_slot(rank_slot.clone());
assert_eq!(
strategy.aggregate_kind(),
AggregateKind::Sum,
"sum(distinct field) should preserve SUM aggregate kind",
);
assert_eq!(
strategy.projected_field(),
"rank",
"sum(distinct field) should preserve projected field labels",
);
assert!(
strategy.aggregate().is_distinct(),
"sum(distinct field) should preserve DISTINCT aggregate shape",
);
assert_eq!(
strategy.target_field(),
&rank_slot,
"sum(distinct field) should preserve the resolved planner field slot",
);
assert_eq!(
strategy.runtime_request(),
PreparedFluentNumericFieldRuntimeRequest::SumDistinct,
"sum(distinct field) should project the numeric DISTINCT runtime request",
);
}
#[test]
fn prepared_fluent_existing_rows_strategy_count_preserves_runtime_shape() {
let strategy = PreparedFluentExistingRowsTerminalStrategy::count_rows();
assert_eq!(
strategy.aggregate().kind(),
AggregateKind::Count,
"count() should preserve the explain-visible aggregate kind",
);
assert_eq!(
strategy.runtime_request(),
&PreparedFluentExistingRowsTerminalRuntimeRequest::CountRows,
"count() should project the existing-rows count runtime request",
);
}
#[test]
fn prepared_fluent_existing_rows_strategy_exists_preserves_runtime_shape() {
let strategy = PreparedFluentExistingRowsTerminalStrategy::exists_rows();
assert_eq!(
strategy.aggregate().kind(),
AggregateKind::Exists,
"exists() should preserve the explain-visible aggregate kind",
);
assert_eq!(
strategy.runtime_request(),
&PreparedFluentExistingRowsTerminalRuntimeRequest::ExistsRows,
"exists() should project the existing-rows exists runtime request",
);
}
#[test]
fn prepared_fluent_numeric_field_strategy_avg_preserves_runtime_shape() {
let rank_slot = FieldSlot::from_parts_for_test(7, "rank");
let strategy = PreparedFluentNumericFieldStrategy::avg_by_slot(rank_slot.clone());
assert_eq!(
strategy.aggregate_kind(),
AggregateKind::Avg,
"avg(field) should preserve AVG aggregate kind",
);
assert_eq!(
strategy.projected_field(),
"rank",
"avg(field) should preserve projected field labels",
);
assert!(
!strategy.aggregate().is_distinct(),
"avg(field) should stay non-distinct unless requested explicitly",
);
assert_eq!(
strategy.target_field(),
&rank_slot,
"avg(field) should preserve the resolved planner field slot",
);
assert_eq!(
strategy.runtime_request(),
PreparedFluentNumericFieldRuntimeRequest::Avg,
"avg(field) should project the numeric AVG runtime request",
);
}
#[test]
fn prepared_fluent_order_sensitive_strategy_first_preserves_explain_and_runtime_shape() {
let strategy = PreparedFluentOrderSensitiveTerminalStrategy::first();
assert_eq!(
strategy
.explain_aggregate()
.map(|aggregate| aggregate.kind()),
Some(AggregateKind::First),
"first() should preserve the explain-visible aggregate kind",
);
assert_eq!(
strategy.runtime_request(),
&PreparedFluentOrderSensitiveTerminalRuntimeRequest::ResponseOrder {
kind: AggregateKind::First,
},
"first() should project the response-order runtime request",
);
}
#[test]
fn prepared_fluent_order_sensitive_strategy_nth_preserves_field_order_runtime_shape() {
let rank_slot = FieldSlot::from_parts_for_test(7, "rank");
let strategy =
PreparedFluentOrderSensitiveTerminalStrategy::nth_by_slot(rank_slot.clone(), 2);
assert_eq!(
strategy.explain_aggregate(),
None,
"nth_by(field, nth) should stay off the current explain aggregate surface",
);
assert_eq!(
strategy.runtime_request(),
&PreparedFluentOrderSensitiveTerminalRuntimeRequest::NthBySlot {
target_field: rank_slot,
nth: 2,
},
"nth_by(field, nth) should preserve the resolved field-order runtime request",
);
}
#[test]
fn prepared_fluent_projection_strategy_count_distinct_preserves_runtime_shape() {
let rank_slot = FieldSlot::from_parts_for_test(7, "rank");
let strategy = PreparedFluentProjectionStrategy::count_distinct_by_slot(rank_slot.clone());
let explain = strategy.explain_descriptor();
assert_eq!(
strategy.target_field(),
&rank_slot,
"count_distinct_by(field) should preserve the resolved planner field slot",
);
assert_eq!(
strategy.runtime_request(),
PreparedFluentProjectionRuntimeRequest::CountDistinct,
"count_distinct_by(field) should project the distinct-count runtime request",
);
assert_eq!(
explain.terminal_label(),
"count_distinct_by",
"count_distinct_by(field) should project the stable explain terminal label",
);
assert_eq!(
explain.field_label(),
"rank",
"count_distinct_by(field) should project the stable explain field label",
);
assert_eq!(
explain.output_label(),
"count",
"count_distinct_by(field) should project the stable explain output label",
);
}
#[test]
fn prepared_fluent_projection_strategy_terminal_value_preserves_runtime_shape() {
let rank_slot = FieldSlot::from_parts_for_test(7, "rank");
let strategy = PreparedFluentProjectionStrategy::last_value_by_slot(rank_slot.clone());
let explain = strategy.explain_descriptor();
assert_eq!(
strategy.target_field(),
&rank_slot,
"last_value_by(field) should preserve the resolved planner field slot",
);
assert_eq!(
strategy.runtime_request(),
PreparedFluentProjectionRuntimeRequest::TerminalValue {
terminal_kind: AggregateKind::Last,
},
"last_value_by(field) should project the terminal-value runtime request",
);
assert_eq!(
explain.terminal_label(),
"last_value_by",
"last_value_by(field) should project the stable explain terminal label",
);
assert_eq!(
explain.field_label(),
"rank",
"last_value_by(field) should project the stable explain field label",
);
assert_eq!(
explain.output_label(),
"terminal_value",
"last_value_by(field) should project the stable explain output label",
);
}
}