use std::fmt::{self, Formatter};
use std::sync::Arc;
use arrow::datatypes::SchemaRef;
use arrow::row::SortField;
use datafusion_common::Result;
use datafusion_common::tree_node::TreeNodeRecursion;
use datafusion_execution::TaskContext;
use datafusion_execution::runtime_env::RuntimeEnv;
use datafusion_physical_expr::PhysicalExpr;
use datafusion_physical_expr_common::sort_expr::LexOrdering;
use futures::StreamExt;
use futures::TryStreamExt;
use crate::execution_plan::{Boundedness, EmissionType};
use crate::metrics::ExecutionPlanMetricsSet;
use crate::topk::{PartitionedTopK, PartitionedTopKRank, build_sort_fields};
use crate::{ChildrenPropertiesMode, ReplaceChildrenOptions};
use crate::{
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, ExecutionPlanProperties,
PlanProperties, SendableRecordBatchStream, stream::RecordBatchStreamAdapter,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowFnKind {
RowNumber,
Rank,
}
#[derive(Debug, Clone)]
pub struct PartitionedTopKExec {
input: Arc<dyn ExecutionPlan>,
expr: LexOrdering,
partition_prefix_len: usize,
fetch: usize,
fn_kind: WindowFnKind,
metrics_set: ExecutionPlanMetricsSet,
cache: Arc<PlanProperties>,
}
impl PartitionedTopKExec {
pub fn try_new(
input: Arc<dyn ExecutionPlan>,
expr: LexOrdering,
partition_prefix_len: usize,
fetch: usize,
fn_kind: WindowFnKind,
) -> Result<Self> {
let cache = Self::compute_properties(&input, expr.clone())?;
Ok(Self {
input,
expr,
partition_prefix_len,
fetch,
fn_kind,
metrics_set: ExecutionPlanMetricsSet::new(),
cache: Arc::new(cache),
})
}
pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
&self.input
}
pub fn expr(&self) -> &LexOrdering {
&self.expr
}
pub fn partition_prefix_len(&self) -> usize {
self.partition_prefix_len
}
pub fn fetch(&self) -> usize {
self.fetch
}
pub fn fn_kind(&self) -> WindowFnKind {
self.fn_kind
}
fn compute_properties(
input: &Arc<dyn ExecutionPlan>,
sort_exprs: LexOrdering,
) -> Result<PlanProperties> {
let mut eq_properties = input.equivalence_properties().clone();
eq_properties.reorder(sort_exprs)?;
Ok(PlanProperties::new(
eq_properties,
input.output_partitioning().clone(),
EmissionType::Final,
Boundedness::Bounded,
))
}
}
impl DisplayAs for PartitionedTopKExec {
fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result {
let fn_label = match self.fn_kind {
WindowFnKind::RowNumber => "row_number",
WindowFnKind::Rank => "rank",
};
match t {
DisplayFormatType::Default | DisplayFormatType::Verbose => {
let partition_exprs: Vec<String> = self.expr[..self.partition_prefix_len]
.iter()
.map(|e| format!("{}", e.expr))
.collect();
let order_exprs: Vec<String> = self.expr[self.partition_prefix_len..]
.iter()
.map(|e| format!("{e}"))
.collect();
write!(
f,
"PartitionedTopKExec: fn={}, fetch={}, partition=[{}], order=[{}]",
fn_label,
self.fetch,
partition_exprs.join(", "),
order_exprs.join(", "),
)
}
DisplayFormatType::TreeRender => {
let partition_exprs: Vec<String> = self.expr[..self.partition_prefix_len]
.iter()
.map(|e| format!("{}", e.expr))
.collect();
let order_exprs: Vec<String> = self.expr[self.partition_prefix_len..]
.iter()
.map(|e| format!("{e}"))
.collect();
writeln!(f, "fn={fn_label}")?;
writeln!(f, "fetch={}", self.fetch)?;
writeln!(f, "partition=[{}]", partition_exprs.join(", "))?;
writeln!(f, "order=[{}]", order_exprs.join(", "))
}
}
}
}
impl ExecutionPlan for PartitionedTopKExec {
fn name(&self) -> &'static str {
"PartitionedTopKExec"
}
fn properties(&self) -> &Arc<PlanProperties> {
&self.cache
}
fn required_input_distribution(&self) -> Vec<Distribution> {
self.input_distribution_requirements().into_per_child()
}
fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
let partition_exprs: Vec<Arc<dyn PhysicalExpr>> = self.expr
[..self.partition_prefix_len]
.iter()
.map(|e| Arc::clone(&e.expr))
.collect();
crate::InputDistributionRequirements::new(vec![Distribution::KeyPartitioned(
partition_exprs,
)])
}
fn maintains_input_order(&self) -> Vec<bool> {
vec![false]
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
vec![&self.input]
}
fn replace_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
_: ReplaceChildrenOptions,
) -> Result<Arc<dyn ExecutionPlan>> {
assert_eq!(children.len(), 1);
Ok(Arc::new(PartitionedTopKExec::try_new(
Arc::clone(&children[0]),
self.expr.clone(),
self.partition_prefix_len,
self.fetch,
self.fn_kind,
)?))
}
fn apply_expressions(
&self,
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
) -> Result<TreeNodeRecursion> {
crate::apply_expression_roots(
self.expr.iter().map(|sort_expr| &sort_expr.expr),
f,
)
}
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
self.replace_children(
children,
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
)
}
fn execute(
&self,
partition: usize,
context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
let input = self.input.execute(partition, Arc::clone(&context))?;
let schema = input.schema();
let partition_sort_fields =
build_sort_fields(&self.expr[..self.partition_prefix_len], &schema)?;
let partition_exprs: Vec<Arc<dyn PhysicalExpr>> = self.expr
[..self.partition_prefix_len]
.iter()
.map(|e| Arc::clone(&e.expr))
.collect();
let order_expr: LexOrdering =
LexOrdering::new(self.expr[self.partition_prefix_len..].iter().cloned())
.expect("PartitionedTopKExec requires at least one order-by expression");
let fetch = self.fetch;
let fn_kind = self.fn_kind;
let batch_size = context.session_config().batch_size();
let runtime = Arc::clone(&context.runtime_env());
let metrics_set = self.metrics_set.clone();
let stream = futures::stream::once(async move {
do_partitioned_topk(
partition,
input,
schema,
partition_exprs,
partition_sort_fields,
order_expr,
fetch,
fn_kind,
batch_size,
runtime,
metrics_set,
)
.await
})
.try_flatten();
Ok(Box::pin(RecordBatchStreamAdapter::new(
self.input.schema(),
stream,
)))
}
}
#[expect(clippy::too_many_arguments)]
async fn do_partitioned_topk(
partition_id: usize,
mut input: SendableRecordBatchStream,
schema: SchemaRef,
partition_exprs: Vec<Arc<dyn PhysicalExpr>>,
partition_sort_fields: Vec<SortField>,
order_expr: LexOrdering,
fetch: usize,
fn_kind: WindowFnKind,
batch_size: usize,
runtime: Arc<RuntimeEnv>,
metrics_set: ExecutionPlanMetricsSet,
) -> Result<SendableRecordBatchStream> {
match fn_kind {
WindowFnKind::RowNumber => {
let mut state = PartitionedTopK::try_new(
partition_id,
schema,
partition_exprs,
partition_sort_fields,
order_expr,
fetch,
batch_size,
&runtime,
&metrics_set,
)?;
while let Some(batch) = input.next().await {
state.insert_batch(&batch?)?;
}
drop(input);
state.emit()
}
WindowFnKind::Rank => {
let mut state = PartitionedTopKRank::try_new(
partition_id,
schema,
partition_exprs,
partition_sort_fields,
order_expr,
fetch,
batch_size,
&runtime,
&metrics_set,
)?;
while let Some(batch) = input.next().await {
state.insert_batch(&batch?)?;
}
drop(input);
state.emit()
}
}
}