datafusion_physical_plan/
empty.rs1use std::sync::Arc;
21
22use crate::memory::MemoryStream;
23use crate::{
24 ChildrenPropertiesMode, DisplayAs, PlanProperties, ReplaceChildrenOptions,
25 SendableRecordBatchStream, Statistics,
26};
27use crate::{
28 DisplayFormatType, ExecutionPlan, Partitioning,
29 execution_plan::{Boundedness, EmissionType},
30};
31
32use arrow::datatypes::SchemaRef;
33use arrow::record_batch::RecordBatch;
34use datafusion_common::stats::Precision;
35use datafusion_common::tree_node::TreeNodeRecursion;
36use datafusion_common::{ColumnStatistics, Result, ScalarValue, assert_or_internal_err};
37use datafusion_execution::TaskContext;
38use datafusion_physical_expr::{EquivalenceProperties, PhysicalExpr};
39
40use crate::execution_plan::SchedulingType;
41use crate::statistics::StatisticsArgs;
42use log::trace;
43
44#[derive(Debug, Clone)]
46pub struct EmptyExec {
47 schema: SchemaRef,
49 partitions: usize,
51 cache: Arc<PlanProperties>,
52}
53
54impl EmptyExec {
55 pub fn new(schema: SchemaRef) -> Self {
57 let cache = Self::compute_properties(Arc::clone(&schema), 1);
58 EmptyExec {
59 schema,
60 partitions: 1,
61 cache: Arc::new(cache),
62 }
63 }
64
65 pub fn with_partitions(mut self, partitions: usize) -> Self {
67 self.partitions = partitions;
68 let output_partitioning = Self::output_partitioning_helper(self.partitions);
70 Arc::make_mut(&mut self.cache).partitioning = output_partitioning;
71 self
72 }
73
74 fn data(&self) -> Result<Vec<RecordBatch>> {
75 Ok(vec![])
76 }
77
78 fn output_partitioning_helper(n_partitions: usize) -> Partitioning {
79 Partitioning::UnknownPartitioning(n_partitions)
80 }
81
82 fn compute_properties(schema: SchemaRef, n_partitions: usize) -> PlanProperties {
84 PlanProperties::new(
85 EquivalenceProperties::new(schema),
86 Self::output_partitioning_helper(n_partitions),
87 EmissionType::Incremental,
88 Boundedness::Bounded,
89 )
90 .with_scheduling_type(SchedulingType::Cooperative)
91 }
92}
93
94impl DisplayAs for EmptyExec {
95 fn fmt_as(
96 &self,
97 t: DisplayFormatType,
98 f: &mut std::fmt::Formatter,
99 ) -> std::fmt::Result {
100 match t {
101 DisplayFormatType::Default | DisplayFormatType::Verbose => {
102 write!(f, "EmptyExec")
103 }
104 DisplayFormatType::TreeRender => {
105 write!(f, "")
107 }
108 }
109 }
110}
111
112impl ExecutionPlan for EmptyExec {
113 fn name(&self) -> &'static str {
114 "EmptyExec"
115 }
116
117 fn properties(&self) -> &Arc<PlanProperties> {
119 &self.cache
120 }
121
122 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
123 vec![]
124 }
125
126 fn apply_expressions(
127 &self,
128 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
129 ) -> Result<TreeNodeRecursion> {
130 Ok(TreeNodeRecursion::Continue)
131 }
132
133 fn replace_children(
134 self: Arc<Self>,
135 _: Vec<Arc<dyn ExecutionPlan>>,
136 _: ReplaceChildrenOptions,
137 ) -> Result<Arc<dyn ExecutionPlan>> {
138 Ok(self)
139 }
140
141 fn with_new_children(
142 self: Arc<Self>,
143 children: Vec<Arc<dyn ExecutionPlan>>,
144 ) -> Result<Arc<dyn ExecutionPlan>> {
145 self.replace_children(
146 children,
147 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
148 )
149 }
150
151 fn execute(
152 &self,
153 partition: usize,
154 context: Arc<TaskContext>,
155 ) -> Result<SendableRecordBatchStream> {
156 trace!(
157 "Start EmptyExec::execute for partition {} of context session_id {} and task_id {:?}",
158 partition,
159 context.session_id(),
160 context.task_id()
161 );
162
163 assert_or_internal_err!(
164 partition < self.partitions,
165 "EmptyExec invalid partition {} (expected less than {})",
166 partition,
167 self.partitions
168 );
169
170 Ok(Box::pin(MemoryStream::try_new(
171 self.data()?,
172 Arc::clone(&self.schema),
173 None,
174 )?))
175 }
176
177 fn statistics_from_inputs(
178 &self,
179 _input_stats: &[Arc<Statistics>],
180 args: &StatisticsArgs,
181 ) -> Result<Arc<Statistics>> {
182 if let Some(partition) = args.partition() {
183 assert_or_internal_err!(
184 partition < self.partitions,
185 "EmptyExec invalid partition {} (expected less than {})",
186 partition,
187 self.partitions
188 );
189 }
190
191 let mut stats = Statistics::default()
193 .with_num_rows(Precision::Exact(0))
194 .with_total_byte_size(Precision::Exact(0));
195
196 for _ in self.schema.fields() {
198 stats = stats.add_column_statistics(ColumnStatistics {
199 null_count: Precision::Exact(0),
200 distinct_count: Precision::Exact(0),
201 min_value: Precision::<ScalarValue>::Absent,
202 max_value: Precision::<ScalarValue>::Absent,
203 sum_value: Precision::<ScalarValue>::Absent,
204 byte_size: Precision::Exact(0),
205 });
206 }
207
208 Ok(Arc::new(stats))
209 }
210
211 #[cfg(feature = "proto")]
212 fn try_to_proto(
213 &self,
214 _ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
215 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
216 use datafusion_proto_models::protobuf;
217 let schema = self.schema().as_ref().try_into()?;
218 Ok(Some(protobuf::PhysicalPlanNode {
219 physical_plan_type: Some(
220 protobuf::physical_plan_node::PhysicalPlanType::Empty(
221 protobuf::EmptyExecNode {
222 schema: Some(schema),
223 partitions: self
224 .properties()
225 .output_partitioning()
226 .partition_count() as u32,
227 },
228 ),
229 ),
230 }))
231 }
232}
233
234#[cfg(feature = "proto")]
235impl EmptyExec {
236 pub fn try_from_proto(
238 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
239 _ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
240 ) -> Result<Arc<dyn ExecutionPlan>> {
241 use datafusion_proto_models::protobuf;
242 let empty = crate::expect_plan_variant!(
243 node,
244 protobuf::physical_plan_node::PhysicalPlanType::Empty,
245 "EmptyExec",
246 );
247 let schema = empty.schema.as_ref().ok_or_else(|| {
248 datafusion_common::internal_datafusion_err!(
249 "EmptyExec is missing required field 'schema'"
250 )
251 })?;
252 let schema = Arc::new(arrow::datatypes::Schema::try_from(schema)?);
253 let partitions = empty.partitions.max(1) as usize;
256 Ok(Arc::new(EmptyExec::new(schema).with_partitions(partitions)))
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::common;
264 use crate::execution_plan::replace_children_if_necessary;
265 use crate::test;
266
267 #[tokio::test]
268 async fn empty() -> Result<()> {
269 let task_ctx = Arc::new(TaskContext::default());
270 let schema = test::aggr_test_schema();
271
272 let empty = EmptyExec::new(Arc::clone(&schema));
273 assert_eq!(empty.schema(), schema);
274
275 let iter = empty.execute(0, task_ctx)?;
277 let batches = common::collect(iter).await?;
278 assert!(batches.is_empty());
279
280 Ok(())
281 }
282
283 #[test]
284 fn with_new_children() -> Result<()> {
285 let schema = test::aggr_test_schema();
286 let empty = Arc::new(EmptyExec::new(Arc::clone(&schema)));
287
288 let empty2 = replace_children_if_necessary(
289 Arc::clone(&empty) as Arc<dyn ExecutionPlan>,
290 vec![],
291 )?;
292 assert_eq!(empty.schema(), empty2.schema());
293
294 let too_many_kids = vec![empty2];
295 assert!(
296 replace_children_if_necessary(empty, too_many_kids).is_err(),
297 "expected error when providing list of kids"
298 );
299 Ok(())
300 }
301
302 #[tokio::test]
303 async fn invalid_execute() -> Result<()> {
304 let task_ctx = Arc::new(TaskContext::default());
305 let schema = test::aggr_test_schema();
306 let empty = EmptyExec::new(schema);
307
308 assert!(empty.execute(1, Arc::clone(&task_ctx)).is_err());
310 assert!(empty.execute(20, task_ctx).is_err());
311 Ok(())
312 }
313}