use std::fmt;
use std::sync::Arc;
use arrow::datatypes::SchemaRef;
use datafusion::error::Result;
use datafusion::execution::TaskContext;
use datafusion::physical_plan::execution_plan::EmissionType;
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
use datafusion::physical_plan::{
DisplayAs, DisplayFormatType, ExecutionPlan, ExecutionPlanProperties, PlanProperties,
SendableRecordBatchStream,
};
use futures::StreamExt;
use oxidelake_core::BackendKind;
use oxidelake_core::params::Predicate;
use oxidelake_core::telemetry::TelemetryHub;
use oxidelake_device::GpuBackend;
use super::{ExecConfig, plan_err, plan_properties, validate_predicate};
use crate::display;
#[derive(Debug)]
pub struct GpuFilterExec {
input: Arc<dyn ExecutionPlan>,
predicate: Predicate,
projection: Vec<usize>,
schema: SchemaRef,
properties: Arc<PlanProperties>,
metrics: ExecutionPlanMetricsSet,
config: ExecConfig,
}
impl GpuFilterExec {
pub fn try_new(
input: Arc<dyn ExecutionPlan>,
predicate: Predicate,
projection: Vec<usize>,
target: BackendKind,
) -> Result<Self> {
let input_schema = input.schema();
validate_predicate(&input_schema, &predicate)?;
if projection.is_empty() {
return plan_err("GpuFilterExec: projection must select at least one column");
}
let schema = Arc::new(input_schema.project(&projection)?);
let properties = plan_properties(
Arc::clone(&schema),
input.output_partitioning().clone(),
EmissionType::Incremental,
);
Ok(Self {
input,
predicate,
projection,
schema,
properties,
metrics: ExecutionPlanMetricsSet::new(),
config: ExecConfig::new(target),
})
}
pub fn with_backend(mut self, backend: Arc<dyn GpuBackend>) -> Self {
self.config.backend = Some(backend);
self
}
pub fn with_telemetry(mut self, telemetry: Arc<TelemetryHub>) -> Self {
self.config.telemetry = Some(telemetry);
self
}
pub fn predicate(&self) -> &Predicate {
&self.predicate
}
pub fn projection(&self) -> &[usize] {
&self.projection
}
pub fn target(&self) -> BackendKind {
self.config.target
}
pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
&self.input
}
fn rebuild(&self, input: Arc<dyn ExecutionPlan>) -> Result<Self> {
let mut exec = Self::try_new(
input,
self.predicate.clone(),
self.projection.clone(),
self.config.target,
)?;
exec.config = self.config.clone();
Ok(exec)
}
}
impl DisplayAs for GpuFilterExec {
fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let input_schema = self.input.schema();
match t {
DisplayFormatType::Default | DisplayFormatType::Verbose => write!(
f,
"GpuFilterExec[{}]: {}, projection=[{}]",
self.config.target,
display::predicate(&self.predicate, &input_schema),
display::projection(&self.projection, &input_schema)
),
DisplayFormatType::TreeRender => write!(
f,
"backend={}\npredicate={}",
self.config.target,
display::predicate(&self.predicate, &input_schema)
),
}
}
}
impl ExecutionPlan for GpuFilterExec {
fn name(&self) -> &str {
"GpuFilterExec"
}
fn properties(&self) -> &Arc<PlanProperties> {
&self.properties
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
vec![&self.input]
}
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
let [input] = <[Arc<dyn ExecutionPlan>; 1]>::try_from(children).map_err(|c| {
datafusion::error::DataFusionError::Plan(format!(
"GpuFilterExec expects 1 child, got {}",
c.len()
))
})?;
Ok(Arc::new(self.rebuild(input)?))
}
fn execute(
&self,
partition: usize,
context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
let input = self.input.execute(partition, context)?;
let operator = self.config.operator("GpuFilterExec")?;
let baseline = BaselineMetrics::new(&self.metrics, partition);
let predicate = self.predicate.clone();
let projection = self.projection.clone();
let stream = input.map(move |batch| {
let batch = batch?;
let _timer = baseline.elapsed_compute().timer();
let out = operator.filter_project(&batch, &predicate, &projection)?;
baseline.record_output(out.num_rows());
Ok(out)
});
Ok(Box::pin(RecordBatchStreamAdapter::new(
Arc::clone(&self.schema),
stream,
)))
}
fn metrics(&self) -> Option<MetricsSet> {
Some(self.metrics.clone_inner())
}
}