use crate::physical_plan::aggregates::{
no_grouping::AggregateStream, row_hash::GroupedHashAggregateStream,
};
use crate::physical_plan::metrics::{ExecutionPlanMetricsSet, MetricsSet};
use crate::physical_plan::{
DisplayFormatType, Distribution, EquivalenceProperties, ExecutionPlan, Partitioning,
SendableRecordBatchStream, Statistics,
};
use arrow::array::ArrayRef;
use arrow::datatypes::{Field, Schema, SchemaRef};
use arrow::record_batch::RecordBatch;
use datafusion_common::utils::longest_consecutive_prefix;
use datafusion_common::{not_impl_err, plan_err, DataFusionError, Result};
use datafusion_execution::TaskContext;
use datafusion_expr::Accumulator;
use datafusion_physical_expr::{
equivalence::project_equivalence_properties,
expressions::Column,
normalize_out_expr_with_columns_map, reverse_order_bys,
utils::{convert_to_expr, get_indices_of_matching_exprs},
AggregateExpr, LexOrdering, LexOrderingReq, OrderingEquivalenceProperties,
PhysicalExpr, PhysicalSortExpr, PhysicalSortRequirement,
};
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
mod group_values;
mod no_grouping;
mod order;
mod row_hash;
pub use datafusion_expr::AggregateFunction;
use datafusion_physical_expr::aggregate::is_order_sensitive;
pub use datafusion_physical_expr::expressions::create_aggregate_expr;
use datafusion_physical_expr::utils::{
get_finer_ordering, ordering_satisfy_requirement_concrete,
};
use super::DisplayAs;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AggregateMode {
Partial,
Final,
FinalPartitioned,
Single,
SinglePartitioned,
}
impl AggregateMode {
fn is_first_stage(&self) -> bool {
match self {
AggregateMode::Partial
| AggregateMode::Single
| AggregateMode::SinglePartitioned => true,
AggregateMode::Final | AggregateMode::FinalPartitioned => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupByOrderMode {
PartiallyOrdered,
FullyOrdered,
}
#[derive(Clone, Debug, Default)]
pub struct PhysicalGroupBy {
expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
null_expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
groups: Vec<Vec<bool>>,
}
impl PhysicalGroupBy {
pub fn new(
expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
null_expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
groups: Vec<Vec<bool>>,
) -> Self {
Self {
expr,
null_expr,
groups,
}
}
pub fn new_single(expr: Vec<(Arc<dyn PhysicalExpr>, String)>) -> Self {
let num_exprs = expr.len();
Self {
expr,
null_expr: vec![],
groups: vec![vec![false; num_exprs]],
}
}
pub fn contains_null(&self) -> bool {
self.groups.iter().flatten().any(|is_null| *is_null)
}
pub fn expr(&self) -> &[(Arc<dyn PhysicalExpr>, String)] {
&self.expr
}
pub fn null_expr(&self) -> &[(Arc<dyn PhysicalExpr>, String)] {
&self.null_expr
}
pub fn groups(&self) -> &[Vec<bool>] {
&self.groups
}
pub fn is_empty(&self) -> bool {
self.expr.is_empty()
}
}
impl PartialEq for PhysicalGroupBy {
fn eq(&self, other: &PhysicalGroupBy) -> bool {
self.expr.len() == other.expr.len()
&& self
.expr
.iter()
.zip(other.expr.iter())
.all(|((expr1, name1), (expr2, name2))| expr1.eq(expr2) && name1 == name2)
&& self.null_expr.len() == other.null_expr.len()
&& self
.null_expr
.iter()
.zip(other.null_expr.iter())
.all(|((expr1, name1), (expr2, name2))| expr1.eq(expr2) && name1 == name2)
&& self.groups == other.groups
}
}
enum StreamType {
AggregateStream(AggregateStream),
GroupedHashAggregateStream(GroupedHashAggregateStream),
}
impl From<StreamType> for SendableRecordBatchStream {
fn from(stream: StreamType) -> Self {
match stream {
StreamType::AggregateStream(stream) => Box::pin(stream),
StreamType::GroupedHashAggregateStream(stream) => Box::pin(stream),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct AggregationOrdering {
mode: GroupByOrderMode,
order_indices: Vec<usize>,
ordering: LexOrdering,
}
#[derive(Debug)]
pub struct AggregateExec {
pub(crate) mode: AggregateMode,
pub(crate) group_by: PhysicalGroupBy,
pub(crate) aggr_expr: Vec<Arc<dyn AggregateExpr>>,
pub(crate) filter_expr: Vec<Option<Arc<dyn PhysicalExpr>>>,
pub(crate) order_by_expr: Vec<Option<LexOrdering>>,
pub(crate) input: Arc<dyn ExecutionPlan>,
schema: SchemaRef,
pub(crate) input_schema: SchemaRef,
columns_map: HashMap<Column, Vec<Column>>,
metrics: ExecutionPlanMetricsSet,
aggregation_ordering: Option<AggregationOrdering>,
required_input_ordering: Option<LexOrderingReq>,
}
fn get_working_mode(
input: &Arc<dyn ExecutionPlan>,
group_by: &PhysicalGroupBy,
) -> Option<(GroupByOrderMode, Vec<usize>)> {
if group_by.groups.len() > 1 {
return None;
};
let output_ordering = input.output_ordering().unwrap_or(&[]);
let ordering_exprs = convert_to_expr(output_ordering);
let groupby_exprs = group_by
.expr
.iter()
.map(|(item, _)| item.clone())
.collect::<Vec<_>>();
let mut ordered_indices =
get_indices_of_matching_exprs(&groupby_exprs, &ordering_exprs, || {
input.equivalence_properties()
});
ordered_indices.sort();
let first_n = longest_consecutive_prefix(ordered_indices);
if first_n == 0 {
return None;
}
let ordered_exprs = ordering_exprs[0..first_n].to_vec();
let ordered_group_by_indices =
get_indices_of_matching_exprs(&ordered_exprs, &groupby_exprs, || {
input.equivalence_properties()
});
Some(if first_n == group_by.expr.len() {
(GroupByOrderMode::FullyOrdered, ordered_group_by_indices)
} else {
(GroupByOrderMode::PartiallyOrdered, ordered_group_by_indices)
})
}
fn calc_aggregation_ordering(
input: &Arc<dyn ExecutionPlan>,
group_by: &PhysicalGroupBy,
) -> Option<AggregationOrdering> {
get_working_mode(input, group_by).map(|(mode, order_indices)| {
let existing_ordering = input.output_ordering().unwrap_or(&[]);
let out_group_expr = output_group_expr_helper(group_by);
let out_ordering = order_indices
.iter()
.zip(existing_ordering)
.map(|(idx, input_col)| PhysicalSortExpr {
expr: out_group_expr[*idx].clone(),
options: input_col.options,
})
.collect::<Vec<_>>();
AggregationOrdering {
mode,
order_indices,
ordering: out_ordering,
}
})
}
fn output_group_expr_helper(group_by: &PhysicalGroupBy) -> Vec<Arc<dyn PhysicalExpr>> {
group_by
.expr()
.iter()
.enumerate()
.map(|(index, (_, name))| Arc::new(Column::new(name, index)) as _)
.collect()
}
fn get_init_req(
aggr_expr: &[Arc<dyn AggregateExpr>],
order_by_expr: &[Option<LexOrdering>],
) -> Option<LexOrdering> {
for (aggr_expr, fn_reqs) in aggr_expr.iter().zip(order_by_expr.iter()) {
if is_order_sensitive(aggr_expr)
&& aggr_expr.reverse_expr().is_none()
&& fn_reqs.is_some()
{
return fn_reqs.clone();
}
}
None
}
fn get_finest_requirement<
F: Fn() -> EquivalenceProperties,
F2: Fn() -> OrderingEquivalenceProperties,
>(
aggr_expr: &mut [Arc<dyn AggregateExpr>],
order_by_expr: &mut [Option<LexOrdering>],
eq_properties: F,
ordering_eq_properties: F2,
) -> Result<Option<LexOrdering>> {
let mut finest_req = get_init_req(aggr_expr, order_by_expr);
for (aggr_expr, fn_req) in aggr_expr.iter_mut().zip(order_by_expr.iter_mut()) {
let fn_req = if let Some(fn_req) = fn_req {
fn_req
} else {
continue;
};
if let Some(finest_req) = &mut finest_req {
if let Some(finer) = get_finer_ordering(
finest_req,
fn_req,
&eq_properties,
&ordering_eq_properties,
) {
*finest_req = finer.to_vec();
continue;
}
if let Some(reverse) = aggr_expr.reverse_expr() {
let fn_req_reverse = reverse_order_bys(fn_req);
if let Some(finer) = get_finer_ordering(
finest_req,
&fn_req_reverse,
&eq_properties,
&ordering_eq_properties,
) {
*aggr_expr = reverse;
*finest_req = finer.to_vec();
*fn_req = fn_req_reverse;
continue;
}
}
return not_impl_err!(
"Conflicting ordering requirements in aggregate functions is not supported"
);
} else {
finest_req = Some(fn_req.clone());
}
}
Ok(finest_req)
}
fn calc_required_input_ordering(
input: &Arc<dyn ExecutionPlan>,
aggr_exprs: &mut [Arc<dyn AggregateExpr>],
order_by_exprs: &mut [Option<LexOrdering>],
aggregator_reqs: LexOrderingReq,
aggregator_reverse_reqs: Option<LexOrderingReq>,
aggregation_ordering: &mut Option<AggregationOrdering>,
mode: &AggregateMode,
) -> Result<Option<LexOrderingReq>> {
let mut required_input_ordering = vec![];
let mut reverse_req = false;
let aggregator_requirements =
if let Some(aggregator_reverse_reqs) = aggregator_reverse_reqs {
vec![(true, aggregator_reverse_reqs), (false, aggregator_reqs)]
} else {
vec![(false, aggregator_reqs)]
};
for (is_reverse, aggregator_requirement) in aggregator_requirements.into_iter() {
if let Some(AggregationOrdering {
mode: GroupByOrderMode::FullyOrdered | GroupByOrderMode::PartiallyOrdered,
order_indices,
..
}) = aggregation_ordering
{
let requirement_prefix =
if let Some(existing_ordering) = input.output_ordering() {
&existing_ordering[0..order_indices.len()]
} else {
&[]
};
let mut requirement =
PhysicalSortRequirement::from_sort_exprs(requirement_prefix.iter());
for req in aggregator_requirement {
if mode.is_first_stage()
&& requirement.iter().all(|item| req.expr.ne(&item.expr))
{
requirement.push(req);
}
}
required_input_ordering = requirement;
} else if mode.is_first_stage() {
required_input_ordering = aggregator_requirement;
}
reverse_req = is_reverse;
let existing_ordering = input.output_ordering().unwrap_or(&[]);
if ordering_satisfy_requirement_concrete(
existing_ordering,
&required_input_ordering,
|| input.equivalence_properties(),
|| input.ordering_equivalence_properties(),
) {
break;
}
}
if reverse_req {
aggr_exprs
.iter_mut()
.zip(order_by_exprs.iter_mut())
.map(|(aggr_expr, ob_expr)| {
if is_order_sensitive(aggr_expr) {
if let Some(reverse) = aggr_expr.reverse_expr() {
*aggr_expr = reverse;
*ob_expr = ob_expr.as_ref().map(|obs| reverse_order_bys(obs));
} else {
return plan_err!(
"Aggregate expression should have a reverse expression"
);
}
}
Ok(())
})
.collect::<Result<Vec<_>>>()?;
}
Ok((!required_input_ordering.is_empty()).then_some(required_input_ordering))
}
impl AggregateExec {
pub fn try_new(
mode: AggregateMode,
group_by: PhysicalGroupBy,
mut aggr_expr: Vec<Arc<dyn AggregateExpr>>,
filter_expr: Vec<Option<Arc<dyn PhysicalExpr>>>,
mut order_by_expr: Vec<Option<LexOrdering>>,
input: Arc<dyn ExecutionPlan>,
input_schema: SchemaRef,
) -> Result<Self> {
let schema = create_schema(
&input.schema(),
&group_by.expr,
&aggr_expr,
group_by.contains_null(),
mode,
)?;
let schema = Arc::new(schema);
order_by_expr = aggr_expr
.iter()
.zip(order_by_expr)
.map(|(aggr_expr, fn_reqs)| {
fn_reqs.filter(|_| is_order_sensitive(aggr_expr) && mode.is_first_stage())
})
.collect::<Vec<_>>();
let mut aggregator_reverse_reqs = None;
let requirement = get_finest_requirement(
&mut aggr_expr,
&mut order_by_expr,
|| input.equivalence_properties(),
|| input.ordering_equivalence_properties(),
)?;
let aggregator_requirement = requirement
.as_ref()
.map(|exprs| PhysicalSortRequirement::from_sort_exprs(exprs.iter()));
let aggregator_reqs = aggregator_requirement.unwrap_or(vec![]);
if aggr_expr
.iter()
.all(|expr| !is_order_sensitive(expr) || expr.reverse_expr().is_some())
{
aggregator_reverse_reqs = requirement.map(|reqs| {
PhysicalSortRequirement::from_sort_exprs(reverse_order_bys(&reqs).iter())
});
}
let mut columns_map: HashMap<Column, Vec<Column>> = HashMap::new();
for (expression, name) in group_by.expr.iter() {
if let Some(column) = expression.as_any().downcast_ref::<Column>() {
let new_col_idx = schema.index_of(name)?;
let entry = columns_map.entry(column.clone()).or_insert_with(Vec::new);
entry.push(Column::new(name, new_col_idx));
};
}
let mut aggregation_ordering = calc_aggregation_ordering(&input, &group_by);
let required_input_ordering = calc_required_input_ordering(
&input,
&mut aggr_expr,
&mut order_by_expr,
aggregator_reqs,
aggregator_reverse_reqs,
&mut aggregation_ordering,
&mode,
)?;
Ok(AggregateExec {
mode,
group_by,
aggr_expr,
filter_expr,
order_by_expr,
input,
schema,
input_schema,
columns_map,
metrics: ExecutionPlanMetricsSet::new(),
aggregation_ordering,
required_input_ordering,
})
}
pub fn mode(&self) -> &AggregateMode {
&self.mode
}
pub fn group_expr(&self) -> &PhysicalGroupBy {
&self.group_by
}
pub fn output_group_expr(&self) -> Vec<Arc<dyn PhysicalExpr>> {
output_group_expr_helper(&self.group_by)
}
pub fn aggr_expr(&self) -> &[Arc<dyn AggregateExpr>] {
&self.aggr_expr
}
pub fn filter_expr(&self) -> &[Option<Arc<dyn PhysicalExpr>>] {
&self.filter_expr
}
pub fn order_by_expr(&self) -> &[Option<LexOrdering>] {
&self.order_by_expr
}
pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
&self.input
}
pub fn input_schema(&self) -> SchemaRef {
self.input_schema.clone()
}
fn execute_typed(
&self,
partition: usize,
context: Arc<TaskContext>,
) -> Result<StreamType> {
if self.group_by.expr.is_empty() {
Ok(StreamType::AggregateStream(AggregateStream::new(
self, context, partition,
)?))
} else {
Ok(StreamType::GroupedHashAggregateStream(
GroupedHashAggregateStream::new(self, context, partition)?,
))
}
}
}
impl DisplayAs for AggregateExec {
fn fmt_as(
&self,
t: DisplayFormatType,
f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
match t {
DisplayFormatType::Default | DisplayFormatType::Verbose => {
write!(f, "AggregateExec: mode={:?}", self.mode)?;
let g: Vec<String> = if self.group_by.groups.len() == 1 {
self.group_by
.expr
.iter()
.map(|(e, alias)| {
let e = e.to_string();
if &e != alias {
format!("{e} as {alias}")
} else {
e
}
})
.collect()
} else {
self.group_by
.groups
.iter()
.map(|group| {
let terms = group
.iter()
.enumerate()
.map(|(idx, is_null)| {
if *is_null {
let (e, alias) = &self.group_by.null_expr[idx];
let e = e.to_string();
if &e != alias {
format!("{e} as {alias}")
} else {
e
}
} else {
let (e, alias) = &self.group_by.expr[idx];
let e = e.to_string();
if &e != alias {
format!("{e} as {alias}")
} else {
e
}
}
})
.collect::<Vec<String>>()
.join(", ");
format!("({terms})")
})
.collect()
};
write!(f, ", gby=[{}]", g.join(", "))?;
let a: Vec<String> = self
.aggr_expr
.iter()
.map(|agg| agg.name().to_string())
.collect();
write!(f, ", aggr=[{}]", a.join(", "))?;
if let Some(aggregation_ordering) = &self.aggregation_ordering {
write!(f, ", ordering_mode={:?}", aggregation_ordering.mode)?;
}
}
}
Ok(())
}
}
impl ExecutionPlan for AggregateExec {
fn as_any(&self) -> &dyn Any {
self
}
fn schema(&self) -> SchemaRef {
self.schema.clone()
}
fn output_partitioning(&self) -> Partitioning {
match &self.mode {
AggregateMode::Partial | AggregateMode::Single => {
let input_partition = self.input.output_partitioning();
match input_partition {
Partitioning::Hash(exprs, part) => {
let normalized_exprs = exprs
.into_iter()
.map(|expr| {
normalize_out_expr_with_columns_map(
expr,
&self.columns_map,
)
})
.collect::<Vec<_>>();
Partitioning::Hash(normalized_exprs, part)
}
_ => input_partition,
}
}
_ => self.input.output_partitioning(),
}
}
fn unbounded_output(&self, children: &[bool]) -> Result<bool> {
if children[0] {
if self.aggregation_ordering.is_none() {
plan_err!(
"Aggregate Error: `GROUP BY` clauses with columns without ordering and GROUPING SETS are not supported for unbounded inputs."
)
} else {
Ok(true)
}
} else {
Ok(false)
}
}
fn output_ordering(&self) -> Option<&[PhysicalSortExpr]> {
self.aggregation_ordering
.as_ref()
.map(|item: &AggregationOrdering| item.ordering.as_slice())
}
fn required_input_distribution(&self) -> Vec<Distribution> {
match &self.mode {
AggregateMode::Partial => {
vec![Distribution::UnspecifiedDistribution]
}
AggregateMode::FinalPartitioned | AggregateMode::SinglePartitioned => {
vec![Distribution::HashPartitioned(self.output_group_expr())]
}
AggregateMode::Final | AggregateMode::Single => {
vec![Distribution::SinglePartition]
}
}
}
fn required_input_ordering(&self) -> Vec<Option<LexOrderingReq>> {
vec![self.required_input_ordering.clone()]
}
fn equivalence_properties(&self) -> EquivalenceProperties {
let mut new_properties = EquivalenceProperties::new(self.schema());
project_equivalence_properties(
self.input.equivalence_properties(),
&self.columns_map,
&mut new_properties,
);
new_properties
}
fn children(&self) -> Vec<Arc<dyn ExecutionPlan>> {
vec![self.input.clone()]
}
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
Ok(Arc::new(AggregateExec::try_new(
self.mode,
self.group_by.clone(),
self.aggr_expr.clone(),
self.filter_expr.clone(),
self.order_by_expr.clone(),
children[0].clone(),
self.input_schema.clone(),
)?))
}
fn execute(
&self,
partition: usize,
context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
self.execute_typed(partition, context)
.map(|stream| stream.into())
}
fn metrics(&self) -> Option<MetricsSet> {
Some(self.metrics.clone_inner())
}
fn statistics(&self) -> Statistics {
match self.mode {
AggregateMode::Final | AggregateMode::FinalPartitioned
if self.group_by.expr.is_empty() =>
{
Statistics {
num_rows: Some(1),
is_exact: true,
..Default::default()
}
}
_ => Statistics {
num_rows: self.input.statistics().num_rows,
is_exact: false,
..Default::default()
},
}
}
}
fn create_schema(
input_schema: &Schema,
group_expr: &[(Arc<dyn PhysicalExpr>, String)],
aggr_expr: &[Arc<dyn AggregateExpr>],
contains_null_expr: bool,
mode: AggregateMode,
) -> Result<Schema> {
let mut fields = Vec::with_capacity(group_expr.len() + aggr_expr.len());
for (expr, name) in group_expr {
fields.push(Field::new(
name,
expr.data_type(input_schema)?,
contains_null_expr || expr.nullable(input_schema)?,
))
}
match mode {
AggregateMode::Partial => {
for expr in aggr_expr {
fields.extend(expr.state_fields()?.iter().cloned())
}
}
AggregateMode::Final
| AggregateMode::FinalPartitioned
| AggregateMode::Single
| AggregateMode::SinglePartitioned => {
for expr in aggr_expr {
fields.push(expr.field()?)
}
}
}
Ok(Schema::new(fields))
}
fn group_schema(schema: &Schema, group_count: usize) -> SchemaRef {
let group_fields = schema.fields()[0..group_count].to_vec();
Arc::new(Schema::new(group_fields))
}
fn aggregate_expressions(
aggr_expr: &[Arc<dyn AggregateExpr>],
mode: &AggregateMode,
col_idx_base: usize,
) -> Result<Vec<Vec<Arc<dyn PhysicalExpr>>>> {
match mode {
AggregateMode::Partial
| AggregateMode::Single
| AggregateMode::SinglePartitioned => Ok(aggr_expr
.iter()
.map(|agg| {
let mut result = agg.expressions().clone();
if matches!(mode, AggregateMode::Partial) {
if let Some(ordering_req) = agg.order_bys() {
let ordering_exprs = ordering_req
.iter()
.map(|item| item.expr.clone())
.collect::<Vec<_>>();
result.extend(ordering_exprs);
}
}
result
})
.collect()),
AggregateMode::Final | AggregateMode::FinalPartitioned => {
let mut col_idx_base = col_idx_base;
Ok(aggr_expr
.iter()
.map(|agg| {
let exprs = merge_expressions(col_idx_base, agg)?;
col_idx_base += exprs.len();
Ok(exprs)
})
.collect::<Result<Vec<_>>>()?)
}
}
}
fn merge_expressions(
index_base: usize,
expr: &Arc<dyn AggregateExpr>,
) -> Result<Vec<Arc<dyn PhysicalExpr>>> {
Ok(expr
.state_fields()?
.iter()
.enumerate()
.map(|(idx, f)| {
Arc::new(Column::new(f.name(), index_base + idx)) as Arc<dyn PhysicalExpr>
})
.collect::<Vec<_>>())
}
pub(crate) type AccumulatorItem = Box<dyn Accumulator>;
fn create_accumulators(
aggr_expr: &[Arc<dyn AggregateExpr>],
) -> Result<Vec<AccumulatorItem>> {
aggr_expr
.iter()
.map(|expr| expr.create_accumulator())
.collect::<Result<Vec<_>>>()
}
fn finalize_aggregation(
accumulators: &[AccumulatorItem],
mode: &AggregateMode,
) -> Result<Vec<ArrayRef>> {
match mode {
AggregateMode::Partial => {
let a = accumulators
.iter()
.map(|accumulator| accumulator.state())
.map(|value| {
value.map(|e| {
e.iter().map(|v| v.to_array()).collect::<Vec<ArrayRef>>()
})
})
.collect::<Result<Vec<_>>>()?;
Ok(a.iter().flatten().cloned().collect::<Vec<_>>())
}
AggregateMode::Final
| AggregateMode::FinalPartitioned
| AggregateMode::Single
| AggregateMode::SinglePartitioned => {
accumulators
.iter()
.map(|accumulator| accumulator.evaluate().map(|v| v.to_array()))
.collect::<Result<Vec<ArrayRef>>>()
}
}
}
fn evaluate(
expr: &[Arc<dyn PhysicalExpr>],
batch: &RecordBatch,
) -> Result<Vec<ArrayRef>> {
expr.iter()
.map(|expr| expr.evaluate(batch))
.map(|r| r.map(|v| v.into_array(batch.num_rows())))
.collect::<Result<Vec<_>>>()
}
fn evaluate_many(
expr: &[Vec<Arc<dyn PhysicalExpr>>],
batch: &RecordBatch,
) -> Result<Vec<Vec<ArrayRef>>> {
expr.iter()
.map(|expr| evaluate(expr, batch))
.collect::<Result<Vec<_>>>()
}
fn evaluate_optional(
expr: &[Option<Arc<dyn PhysicalExpr>>],
batch: &RecordBatch,
) -> Result<Vec<Option<ArrayRef>>> {
expr.iter()
.map(|expr| {
expr.as_ref()
.map(|expr| expr.evaluate(batch))
.transpose()
.map(|r| r.map(|v| v.into_array(batch.num_rows())))
})
.collect::<Result<Vec<_>>>()
}
fn evaluate_group_by(
group_by: &PhysicalGroupBy,
batch: &RecordBatch,
) -> Result<Vec<Vec<ArrayRef>>> {
let exprs: Vec<ArrayRef> = group_by
.expr
.iter()
.map(|(expr, _)| {
let value = expr.evaluate(batch)?;
Ok(value.into_array(batch.num_rows()))
})
.collect::<Result<Vec<_>>>()?;
let null_exprs: Vec<ArrayRef> = group_by
.null_expr
.iter()
.map(|(expr, _)| {
let value = expr.evaluate(batch)?;
Ok(value.into_array(batch.num_rows()))
})
.collect::<Result<Vec<_>>>()?;
Ok(group_by
.groups
.iter()
.map(|group| {
group
.iter()
.enumerate()
.map(|(idx, is_null)| {
if *is_null {
null_exprs[idx].clone()
} else {
exprs[idx].clone()
}
})
.collect()
})
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::physical_plan::aggregates::GroupByOrderMode::{
FullyOrdered, PartiallyOrdered,
};
use crate::physical_plan::aggregates::{
get_finest_requirement, get_working_mode, AggregateExec, AggregateMode,
PhysicalGroupBy,
};
use crate::physical_plan::coalesce_batches::CoalesceBatchesExec;
use crate::physical_plan::coalesce_partitions::CoalescePartitionsExec;
use crate::physical_plan::expressions::{col, Avg};
use crate::physical_plan::memory::MemoryExec;
use crate::physical_plan::{
DisplayAs, ExecutionPlan, Partitioning, RecordBatchStream,
SendableRecordBatchStream, Statistics,
};
use crate::test::exec::{assert_strong_count_converges_to_zero, BlockingExec};
use crate::test::{assert_is_pending, csv_exec_sorted};
use crate::{assert_batches_eq, assert_batches_sorted_eq, physical_plan::common};
use arrow::array::{Float64Array, UInt32Array};
use arrow::compute::{concat_batches, SortOptions};
use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
use arrow::record_batch::RecordBatch;
use datafusion_common::{internal_err, DataFusionError, Result, ScalarValue};
use datafusion_execution::runtime_env::{RuntimeConfig, RuntimeEnv};
use datafusion_physical_expr::expressions::{
lit, ApproxDistinct, Column, Count, FirstValue, LastValue, Median,
};
use datafusion_physical_expr::{
AggregateExpr, EquivalenceProperties, OrderingEquivalenceProperties,
PhysicalExpr, PhysicalSortExpr,
};
use std::any::Any;
use std::sync::Arc;
use std::task::{Context, Poll};
use futures::{FutureExt, Stream};
fn create_test_schema() -> Result<SchemaRef> {
let a = Field::new("a", DataType::Int32, true);
let b = Field::new("b", DataType::Int32, true);
let c = Field::new("c", DataType::Int32, true);
let d = Field::new("d", DataType::Int32, true);
let e = Field::new("e", DataType::Int32, true);
let schema = Arc::new(Schema::new(vec![a, b, c, d, e]));
Ok(schema)
}
fn sort_expr(name: &str, schema: &Schema) -> PhysicalSortExpr {
sort_expr_options(name, schema, SortOptions::default())
}
fn sort_expr_options(
name: &str,
schema: &Schema,
options: SortOptions,
) -> PhysicalSortExpr {
PhysicalSortExpr {
expr: col(name, schema).unwrap(),
options,
}
}
#[tokio::test]
async fn test_get_working_mode() -> Result<()> {
let test_schema = create_test_schema()?;
let sort_exprs = vec![
sort_expr("a", &test_schema),
sort_expr("b", &test_schema),
sort_expr("c", &test_schema),
];
let input = csv_exec_sorted(&test_schema, sort_exprs, true);
let test_cases = vec![
(vec!["a"], Some((FullyOrdered, vec![0]))),
(vec!["b"], None),
(vec!["c"], None),
(vec!["b", "a"], Some((FullyOrdered, vec![1, 0]))),
(vec!["c", "b"], None),
(vec!["c", "a"], Some((PartiallyOrdered, vec![1]))),
(vec!["c", "b", "a"], Some((FullyOrdered, vec![2, 1, 0]))),
(vec!["d", "a"], Some((PartiallyOrdered, vec![1]))),
(vec!["d", "b"], None),
(vec!["d", "c"], None),
(vec!["d", "b", "a"], Some((PartiallyOrdered, vec![2, 1]))),
(vec!["d", "c", "b"], None),
(vec!["d", "c", "a"], Some((PartiallyOrdered, vec![2]))),
(
vec!["d", "c", "b", "a"],
Some((PartiallyOrdered, vec![3, 2, 1])),
),
];
for (case_idx, test_case) in test_cases.iter().enumerate() {
let (group_by_columns, expected) = &test_case;
let mut group_by_exprs = vec![];
for col_name in group_by_columns {
group_by_exprs.push((col(col_name, &test_schema)?, col_name.to_string()));
}
let group_bys = PhysicalGroupBy::new_single(group_by_exprs);
let res = get_working_mode(&input, &group_bys);
assert_eq!(
res, *expected,
"Unexpected result for in unbounded test case#: {case_idx:?}, case: {test_case:?}"
);
}
Ok(())
}
fn some_data() -> (Arc<Schema>, Vec<RecordBatch>) {
let schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::UInt32, false),
Field::new("b", DataType::Float64, false),
]));
(
schema.clone(),
vec![
RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(UInt32Array::from(vec![2, 3, 4, 4])),
Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
],
)
.unwrap(),
RecordBatch::try_new(
schema,
vec![
Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
],
)
.unwrap(),
],
)
}
fn some_data_v2() -> (Arc<Schema>, Vec<RecordBatch>) {
let schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::UInt32, false),
Field::new("b", DataType::Float64, false),
]));
(
schema.clone(),
vec![
RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(UInt32Array::from(vec![2, 3, 4, 4])),
Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
],
)
.unwrap(),
RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
Arc::new(Float64Array::from(vec![0.0, 1.0, 2.0, 3.0])),
],
)
.unwrap(),
RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
Arc::new(Float64Array::from(vec![3.0, 4.0, 5.0, 6.0])),
],
)
.unwrap(),
RecordBatch::try_new(
schema,
vec![
Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
Arc::new(Float64Array::from(vec![2.0, 3.0, 4.0, 5.0])),
],
)
.unwrap(),
],
)
}
async fn check_grouping_sets(input: Arc<dyn ExecutionPlan>) -> Result<()> {
let input_schema = input.schema();
let grouping_set = PhysicalGroupBy {
expr: vec![
(col("a", &input_schema)?, "a".to_string()),
(col("b", &input_schema)?, "b".to_string()),
],
null_expr: vec![
(lit(ScalarValue::UInt32(None)), "a".to_string()),
(lit(ScalarValue::Float64(None)), "b".to_string()),
],
groups: vec![
vec![false, true], vec![true, false], vec![false, false], ],
};
let aggregates: Vec<Arc<dyn AggregateExpr>> = vec![Arc::new(Count::new(
lit(1i8),
"COUNT(1)".to_string(),
DataType::Int64,
))];
let task_ctx = Arc::new(TaskContext::default());
let partial_aggregate = Arc::new(AggregateExec::try_new(
AggregateMode::Partial,
grouping_set.clone(),
aggregates.clone(),
vec![None],
vec![None],
input,
input_schema.clone(),
)?);
let result =
common::collect(partial_aggregate.execute(0, task_ctx.clone())?).await?;
let expected = vec![
"+---+-----+-----------------+",
"| a | b | COUNT(1)[count] |",
"+---+-----+-----------------+",
"| | 1.0 | 2 |",
"| | 2.0 | 2 |",
"| | 3.0 | 2 |",
"| | 4.0 | 2 |",
"| 2 | | 2 |",
"| 2 | 1.0 | 2 |",
"| 3 | | 3 |",
"| 3 | 2.0 | 2 |",
"| 3 | 3.0 | 1 |",
"| 4 | | 3 |",
"| 4 | 3.0 | 1 |",
"| 4 | 4.0 | 2 |",
"+---+-----+-----------------+",
];
assert_batches_sorted_eq!(expected, &result);
let groups = partial_aggregate.group_expr().expr().to_vec();
let merge = Arc::new(CoalescePartitionsExec::new(partial_aggregate));
let final_group: Vec<(Arc<dyn PhysicalExpr>, String)> = groups
.iter()
.map(|(_expr, name)| Ok((col(name, &input_schema)?, name.clone())))
.collect::<Result<_>>()?;
let final_grouping_set = PhysicalGroupBy::new_single(final_group);
let merged_aggregate = Arc::new(AggregateExec::try_new(
AggregateMode::Final,
final_grouping_set,
aggregates,
vec![None],
vec![None],
merge,
input_schema,
)?);
let result =
common::collect(merged_aggregate.execute(0, task_ctx.clone())?).await?;
let batch = concat_batches(&result[0].schema(), &result)?;
assert_eq!(batch.num_columns(), 3);
assert_eq!(batch.num_rows(), 12);
let expected = vec![
"+---+-----+----------+",
"| a | b | COUNT(1) |",
"+---+-----+----------+",
"| | 1.0 | 2 |",
"| | 2.0 | 2 |",
"| | 3.0 | 2 |",
"| | 4.0 | 2 |",
"| 2 | | 2 |",
"| 2 | 1.0 | 2 |",
"| 3 | | 3 |",
"| 3 | 2.0 | 2 |",
"| 3 | 3.0 | 1 |",
"| 4 | | 3 |",
"| 4 | 3.0 | 1 |",
"| 4 | 4.0 | 2 |",
"+---+-----+----------+",
];
assert_batches_sorted_eq!(&expected, &result);
let metrics = merged_aggregate.metrics().unwrap();
let output_rows = metrics.output_rows().unwrap();
assert_eq!(12, output_rows);
Ok(())
}
async fn check_aggregates(input: Arc<dyn ExecutionPlan>) -> Result<()> {
let input_schema = input.schema();
let grouping_set = PhysicalGroupBy {
expr: vec![(col("a", &input_schema)?, "a".to_string())],
null_expr: vec![],
groups: vec![vec![false]],
};
let aggregates: Vec<Arc<dyn AggregateExpr>> = vec![Arc::new(Avg::new(
col("b", &input_schema)?,
"AVG(b)".to_string(),
DataType::Float64,
))];
let task_ctx = Arc::new(TaskContext::default());
let partial_aggregate = Arc::new(AggregateExec::try_new(
AggregateMode::Partial,
grouping_set.clone(),
aggregates.clone(),
vec![None],
vec![None],
input,
input_schema.clone(),
)?);
let result =
common::collect(partial_aggregate.execute(0, task_ctx.clone())?).await?;
let expected = [
"+---+---------------+-------------+",
"| a | AVG(b)[count] | AVG(b)[sum] |",
"+---+---------------+-------------+",
"| 2 | 2 | 2.0 |",
"| 3 | 3 | 7.0 |",
"| 4 | 3 | 11.0 |",
"+---+---------------+-------------+",
];
assert_batches_sorted_eq!(expected, &result);
let merge = Arc::new(CoalescePartitionsExec::new(partial_aggregate));
let final_group: Vec<(Arc<dyn PhysicalExpr>, String)> = grouping_set
.expr
.iter()
.map(|(_expr, name)| Ok((col(name, &input_schema)?, name.clone())))
.collect::<Result<_>>()?;
let final_grouping_set = PhysicalGroupBy::new_single(final_group);
let merged_aggregate = Arc::new(AggregateExec::try_new(
AggregateMode::Final,
final_grouping_set,
aggregates,
vec![None],
vec![None],
merge,
input_schema,
)?);
let result =
common::collect(merged_aggregate.execute(0, task_ctx.clone())?).await?;
let batch = concat_batches(&result[0].schema(), &result)?;
assert_eq!(batch.num_columns(), 2);
assert_eq!(batch.num_rows(), 3);
let expected = vec![
"+---+--------------------+",
"| a | AVG(b) |",
"+---+--------------------+",
"| 2 | 1.0 |",
"| 3 | 2.3333333333333335 |", "| 4 | 3.6666666666666665 |", "+---+--------------------+",
];
assert_batches_sorted_eq!(&expected, &result);
let metrics = merged_aggregate.metrics().unwrap();
let output_rows = metrics.output_rows().unwrap();
assert_eq!(3, output_rows);
Ok(())
}
#[derive(Debug)]
struct TestYieldingExec {
pub yield_first: bool,
}
impl DisplayAs for TestYieldingExec {
fn fmt_as(
&self,
t: DisplayFormatType,
f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
match t {
DisplayFormatType::Default | DisplayFormatType::Verbose => {
write!(f, "TestYieldingExec")
}
}
}
}
impl ExecutionPlan for TestYieldingExec {
fn as_any(&self) -> &dyn Any {
self
}
fn schema(&self) -> SchemaRef {
some_data().0
}
fn output_partitioning(&self) -> Partitioning {
Partitioning::UnknownPartitioning(1)
}
fn output_ordering(&self) -> Option<&[PhysicalSortExpr]> {
None
}
fn children(&self) -> Vec<Arc<dyn ExecutionPlan>> {
vec![]
}
fn with_new_children(
self: Arc<Self>,
_: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
internal_err!("Children cannot be replaced in {self:?}")
}
fn execute(
&self,
_partition: usize,
_context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
let stream = if self.yield_first {
TestYieldingStream::New
} else {
TestYieldingStream::Yielded
};
Ok(Box::pin(stream))
}
fn statistics(&self) -> Statistics {
let (_, batches) = some_data();
common::compute_record_batch_statistics(&[batches], &self.schema(), None)
}
}
enum TestYieldingStream {
New,
Yielded,
ReturnedBatch1,
ReturnedBatch2,
}
impl Stream for TestYieldingStream {
type Item = Result<RecordBatch>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match &*self {
TestYieldingStream::New => {
*(self.as_mut()) = TestYieldingStream::Yielded;
cx.waker().wake_by_ref();
Poll::Pending
}
TestYieldingStream::Yielded => {
*(self.as_mut()) = TestYieldingStream::ReturnedBatch1;
Poll::Ready(Some(Ok(some_data().1[0].clone())))
}
TestYieldingStream::ReturnedBatch1 => {
*(self.as_mut()) = TestYieldingStream::ReturnedBatch2;
Poll::Ready(Some(Ok(some_data().1[1].clone())))
}
TestYieldingStream::ReturnedBatch2 => Poll::Ready(None),
}
}
}
impl RecordBatchStream for TestYieldingStream {
fn schema(&self) -> SchemaRef {
some_data().0
}
}
#[tokio::test]
async fn aggregate_source_not_yielding() -> Result<()> {
let input: Arc<dyn ExecutionPlan> =
Arc::new(TestYieldingExec { yield_first: false });
check_aggregates(input).await
}
#[tokio::test]
async fn aggregate_grouping_sets_source_not_yielding() -> Result<()> {
let input: Arc<dyn ExecutionPlan> =
Arc::new(TestYieldingExec { yield_first: false });
check_grouping_sets(input).await
}
#[tokio::test]
async fn aggregate_source_with_yielding() -> Result<()> {
let input: Arc<dyn ExecutionPlan> =
Arc::new(TestYieldingExec { yield_first: true });
check_aggregates(input).await
}
#[tokio::test]
async fn aggregate_grouping_sets_with_yielding() -> Result<()> {
let input: Arc<dyn ExecutionPlan> =
Arc::new(TestYieldingExec { yield_first: true });
check_grouping_sets(input).await
}
#[tokio::test]
async fn test_oom() -> Result<()> {
let input: Arc<dyn ExecutionPlan> =
Arc::new(TestYieldingExec { yield_first: true });
let input_schema = input.schema();
let runtime = Arc::new(
RuntimeEnv::new(RuntimeConfig::default().with_memory_limit(1, 1.0)).unwrap(),
);
let task_ctx = TaskContext::default().with_runtime(runtime);
let task_ctx = Arc::new(task_ctx);
let groups_none = PhysicalGroupBy::default();
let groups_some = PhysicalGroupBy {
expr: vec![(col("a", &input_schema)?, "a".to_string())],
null_expr: vec![],
groups: vec![vec![false]],
};
let aggregates_v0: Vec<Arc<dyn AggregateExpr>> = vec![Arc::new(Median::new(
col("a", &input_schema)?,
"MEDIAN(a)".to_string(),
DataType::UInt32,
))];
let aggregates_v1: Vec<Arc<dyn AggregateExpr>> =
vec![Arc::new(ApproxDistinct::new(
col("a", &input_schema)?,
"APPROX_DISTINCT(a)".to_string(),
DataType::UInt32,
))];
let aggregates_v2: Vec<Arc<dyn AggregateExpr>> = vec![Arc::new(Avg::new(
col("b", &input_schema)?,
"AVG(b)".to_string(),
DataType::Float64,
))];
for (version, groups, aggregates) in [
(0, groups_none, aggregates_v0),
(1, groups_some.clone(), aggregates_v1),
(2, groups_some, aggregates_v2),
] {
let partial_aggregate = Arc::new(AggregateExec::try_new(
AggregateMode::Partial,
groups,
aggregates,
vec![None; 3],
vec![None; 3],
input.clone(),
input_schema.clone(),
)?);
let stream = partial_aggregate.execute_typed(0, task_ctx.clone())?;
match version {
0 => {
assert!(matches!(stream, StreamType::AggregateStream(_)));
}
1 => {
assert!(matches!(stream, StreamType::GroupedHashAggregateStream(_)));
}
2 => {
assert!(matches!(stream, StreamType::GroupedHashAggregateStream(_)));
}
_ => panic!("Unknown version: {version}"),
}
let stream: SendableRecordBatchStream = stream.into();
let err = common::collect(stream).await.unwrap_err();
let err = err.find_root();
assert!(
matches!(err, DataFusionError::ResourcesExhausted(_)),
"Wrong error type: {err}",
);
}
Ok(())
}
#[tokio::test]
async fn test_drop_cancel_without_groups() -> Result<()> {
let task_ctx = Arc::new(TaskContext::default());
let schema =
Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
let groups = PhysicalGroupBy::default();
let aggregates: Vec<Arc<dyn AggregateExpr>> = vec![Arc::new(Avg::new(
col("a", &schema)?,
"AVG(a)".to_string(),
DataType::Float64,
))];
let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
let refs = blocking_exec.refs();
let aggregate_exec = Arc::new(AggregateExec::try_new(
AggregateMode::Partial,
groups.clone(),
aggregates.clone(),
vec![None],
vec![None],
blocking_exec,
schema,
)?);
let fut = crate::physical_plan::collect(aggregate_exec, task_ctx);
let mut fut = fut.boxed();
assert_is_pending(&mut fut);
drop(fut);
assert_strong_count_converges_to_zero(refs).await;
Ok(())
}
#[tokio::test]
async fn test_drop_cancel_with_groups() -> Result<()> {
let task_ctx = Arc::new(TaskContext::default());
let schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::Float32, true),
Field::new("b", DataType::Float32, true),
]));
let groups =
PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
let aggregates: Vec<Arc<dyn AggregateExpr>> = vec![Arc::new(Avg::new(
col("b", &schema)?,
"AVG(b)".to_string(),
DataType::Float64,
))];
let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
let refs = blocking_exec.refs();
let aggregate_exec = Arc::new(AggregateExec::try_new(
AggregateMode::Partial,
groups,
aggregates.clone(),
vec![None],
vec![None],
blocking_exec,
schema,
)?);
let fut = crate::physical_plan::collect(aggregate_exec, task_ctx);
let mut fut = fut.boxed();
assert_is_pending(&mut fut);
drop(fut);
assert_strong_count_converges_to_zero(refs).await;
Ok(())
}
#[tokio::test]
async fn run_first_last_multi_partitions() -> Result<()> {
for use_coalesce_batches in [false, true] {
for is_first_acc in [false, true] {
first_last_multi_partitions(use_coalesce_batches, is_first_acc).await?
}
}
Ok(())
}
async fn first_last_multi_partitions(
use_coalesce_batches: bool,
is_first_acc: bool,
) -> Result<()> {
let task_ctx = Arc::new(TaskContext::default());
let (schema, data) = some_data_v2();
let partition1 = data[0].clone();
let partition2 = data[1].clone();
let partition3 = data[2].clone();
let partition4 = data[3].clone();
let groups =
PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
let ordering_req = vec![PhysicalSortExpr {
expr: col("b", &schema)?,
options: SortOptions::default(),
}];
let aggregates: Vec<Arc<dyn AggregateExpr>> = if is_first_acc {
vec![Arc::new(FirstValue::new(
col("b", &schema)?,
"FIRST_VALUE(b)".to_string(),
DataType::Float64,
ordering_req.clone(),
vec![DataType::Float64],
))]
} else {
vec![Arc::new(LastValue::new(
col("b", &schema)?,
"LAST_VALUE(b)".to_string(),
DataType::Float64,
ordering_req.clone(),
vec![DataType::Float64],
))]
};
let memory_exec = Arc::new(MemoryExec::try_new(
&[
vec![partition1],
vec![partition2],
vec![partition3],
vec![partition4],
],
schema.clone(),
None,
)?);
let aggregate_exec = Arc::new(AggregateExec::try_new(
AggregateMode::Partial,
groups.clone(),
aggregates.clone(),
vec![None],
vec![Some(ordering_req.clone())],
memory_exec,
schema.clone(),
)?);
let coalesce = if use_coalesce_batches {
let coalesce = Arc::new(CoalescePartitionsExec::new(aggregate_exec));
Arc::new(CoalesceBatchesExec::new(coalesce, 1024)) as Arc<dyn ExecutionPlan>
} else {
Arc::new(CoalescePartitionsExec::new(aggregate_exec))
as Arc<dyn ExecutionPlan>
};
let aggregate_final = Arc::new(AggregateExec::try_new(
AggregateMode::Final,
groups,
aggregates.clone(),
vec![None],
vec![Some(ordering_req)],
coalesce,
schema,
)?) as Arc<dyn ExecutionPlan>;
let result = crate::physical_plan::collect(aggregate_final, task_ctx).await?;
if is_first_acc {
let expected = [
"+---+----------------+",
"| a | FIRST_VALUE(b) |",
"+---+----------------+",
"| 2 | 0.0 |",
"| 3 | 1.0 |",
"| 4 | 3.0 |",
"+---+----------------+",
];
assert_batches_eq!(expected, &result);
} else {
let expected = [
"+---+---------------+",
"| a | LAST_VALUE(b) |",
"+---+---------------+",
"| 2 | 3.0 |",
"| 3 | 5.0 |",
"| 4 | 6.0 |",
"+---+---------------+",
];
assert_batches_eq!(expected, &result);
};
Ok(())
}
#[tokio::test]
async fn test_get_finest_requirements() -> Result<()> {
let test_schema = create_test_schema()?;
let options1 = SortOptions {
descending: false,
nulls_first: false,
};
let options2 = SortOptions {
descending: true,
nulls_first: true,
};
let mut eq_properties = EquivalenceProperties::new(test_schema.clone());
let col_a = Column::new("a", 0);
let col_b = Column::new("b", 1);
let col_c = Column::new("c", 2);
let col_d = Column::new("d", 3);
eq_properties.add_equal_conditions((&col_a, &col_b));
let mut ordering_eq_properties = OrderingEquivalenceProperties::new(test_schema);
ordering_eq_properties.add_equal_conditions((
&vec![PhysicalSortExpr {
expr: Arc::new(col_a.clone()) as _,
options: options1,
}],
&vec![PhysicalSortExpr {
expr: Arc::new(col_c.clone()) as _,
options: options2,
}],
));
let mut order_by_exprs = vec![
None,
Some(vec![PhysicalSortExpr {
expr: Arc::new(col_a.clone()),
options: options1,
}]),
Some(vec![PhysicalSortExpr {
expr: Arc::new(col_b.clone()),
options: options1,
}]),
Some(vec![PhysicalSortExpr {
expr: Arc::new(col_c),
options: options2,
}]),
Some(vec![
PhysicalSortExpr {
expr: Arc::new(col_a.clone()),
options: options1,
},
PhysicalSortExpr {
expr: Arc::new(col_d),
options: options1,
},
]),
Some(vec![PhysicalSortExpr {
expr: Arc::new(col_b.clone()),
options: options2,
}]),
];
let aggr_expr = Arc::new(FirstValue::new(
Arc::new(col_a.clone()),
"first1",
DataType::Int32,
vec![],
vec![],
)) as _;
let mut aggr_exprs = vec![aggr_expr; order_by_exprs.len()];
let res = get_finest_requirement(
&mut aggr_exprs,
&mut order_by_exprs,
|| eq_properties.clone(),
|| ordering_eq_properties.clone(),
)?;
assert_eq!(res, order_by_exprs[4]);
Ok(())
}
}