datafusion_physical_plan/
work_table.rs1use std::any::Any;
21use std::sync::{Arc, Mutex};
22
23use crate::coop::cooperative;
24use crate::execution_plan::{Boundedness, EmissionType, SchedulingType};
25use crate::memory::MemoryStream;
26use crate::metrics::{ExecutionPlanMetricsSet, MetricsSet};
27use crate::{
28 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
29 ReplaceChildrenOptions, SendableRecordBatchStream, Statistics,
30};
31
32use crate::statistics::StatisticsArgs;
33use arrow::datatypes::SchemaRef;
34use arrow::record_batch::RecordBatch;
35use datafusion_common::tree_node::TreeNodeRecursion;
36use datafusion_common::{Result, assert_eq_or_internal_err, internal_datafusion_err};
37use datafusion_execution::TaskContext;
38use datafusion_execution::memory_pool::MemoryReservation;
39use datafusion_physical_expr::{EquivalenceProperties, Partitioning, PhysicalExpr};
40
41#[derive(Debug)]
43pub(super) struct ReservedBatches {
44 batches: Vec<RecordBatch>,
45 reservation: MemoryReservation,
46}
47
48impl ReservedBatches {
49 pub(super) fn new(batches: Vec<RecordBatch>, reservation: MemoryReservation) -> Self {
50 ReservedBatches {
51 batches,
52 reservation,
53 }
54 }
55}
56
57#[derive(Debug)]
61pub struct WorkTable {
62 batches: Mutex<Option<ReservedBatches>>,
63 name: String,
64}
65
66impl WorkTable {
67 pub(super) fn new(name: String) -> Self {
69 Self {
70 batches: Mutex::new(None),
71 name,
72 }
73 }
74
75 fn take(&self) -> Result<ReservedBatches> {
78 self.batches
79 .lock()
80 .unwrap()
81 .take()
82 .ok_or_else(|| internal_datafusion_err!("Unexpected empty work table"))
83 }
84
85 pub(super) fn update(&self, batches: ReservedBatches) {
87 self.batches.lock().unwrap().replace(batches);
88 }
89}
90
91#[derive(Clone, Debug)]
102pub struct WorkTableExec {
103 name: String,
105 schema: SchemaRef,
107 projection: Option<Vec<usize>>,
109 work_table: Arc<WorkTable>,
111 metrics: ExecutionPlanMetricsSet,
113 cache: Arc<PlanProperties>,
115}
116
117impl WorkTableExec {
118 pub fn new(
120 name: String,
121 mut schema: SchemaRef,
122 projection: Option<Vec<usize>>,
123 ) -> Result<Self> {
124 if let Some(projection) = &projection {
125 schema = Arc::new(schema.project(projection)?);
126 }
127 let cache = Self::compute_properties(Arc::clone(&schema));
128 Ok(Self {
129 name: name.clone(),
130 schema,
131 projection,
132 work_table: Arc::new(WorkTable::new(name)),
133 metrics: ExecutionPlanMetricsSet::new(),
134 cache: Arc::new(cache),
135 })
136 }
137
138 pub fn name(&self) -> &str {
140 &self.name
141 }
142
143 pub fn schema(&self) -> SchemaRef {
145 Arc::clone(&self.schema)
146 }
147
148 fn compute_properties(schema: SchemaRef) -> PlanProperties {
150 PlanProperties::new(
151 EquivalenceProperties::new(schema),
152 Partitioning::UnknownPartitioning(1),
153 EmissionType::Incremental,
154 Boundedness::Bounded,
155 )
156 .with_scheduling_type(SchedulingType::Cooperative)
157 }
158}
159
160impl DisplayAs for WorkTableExec {
161 fn fmt_as(
162 &self,
163 t: DisplayFormatType,
164 f: &mut std::fmt::Formatter,
165 ) -> std::fmt::Result {
166 match t {
167 DisplayFormatType::Default | DisplayFormatType::Verbose => {
168 write!(f, "WorkTableExec: name={}", self.name)
169 }
170 DisplayFormatType::TreeRender => {
171 write!(f, "name={}", self.name)
172 }
173 }
174 }
175}
176
177impl ExecutionPlan for WorkTableExec {
178 fn name(&self) -> &'static str {
179 "WorkTableExec"
180 }
181
182 fn properties(&self) -> &Arc<PlanProperties> {
183 &self.cache
184 }
185
186 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
187 vec![]
188 }
189
190 fn apply_expressions(
191 &self,
192 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
193 ) -> Result<TreeNodeRecursion> {
194 Ok(TreeNodeRecursion::Continue)
195 }
196
197 fn replace_children(
198 self: Arc<Self>,
199 _: Vec<Arc<dyn ExecutionPlan>>,
200 _: ReplaceChildrenOptions,
201 ) -> Result<Arc<dyn ExecutionPlan>> {
202 Ok(Arc::clone(&self) as Arc<dyn ExecutionPlan>)
203 }
204
205 fn with_new_children(
206 self: Arc<Self>,
207 children: Vec<Arc<dyn ExecutionPlan>>,
208 ) -> Result<Arc<dyn ExecutionPlan>> {
209 self.replace_children(
210 children,
211 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
212 )
213 }
214
215 fn execute(
217 &self,
218 partition: usize,
219 _context: Arc<TaskContext>,
220 ) -> Result<SendableRecordBatchStream> {
221 assert_eq_or_internal_err!(
223 partition,
224 0,
225 "WorkTableExec got an invalid partition {partition} (expected 0)"
226 );
227 let ReservedBatches {
228 mut batches,
229 reservation,
230 } = self.work_table.take()?;
231 if let Some(projection) = &self.projection {
232 batches = batches
236 .into_iter()
237 .map(|b| b.project(projection))
238 .collect::<Result<Vec<_>, _>>()?;
239 }
240
241 let stream = MemoryStream::try_new(batches, Arc::clone(&self.schema), None)?
242 .with_reservation(reservation);
243 Ok(Box::pin(cooperative(stream)))
244 }
245
246 fn metrics(&self) -> Option<MetricsSet> {
247 Some(self.metrics.clone_inner())
248 }
249
250 fn statistics_from_inputs(
251 &self,
252 _input_stats: &[Arc<Statistics>],
253 _args: &StatisticsArgs,
254 ) -> Result<Arc<Statistics>> {
255 Ok(Arc::new(Statistics::new_unknown(&self.schema())))
256 }
257
258 fn with_new_state(
266 &self,
267 state: Arc<dyn Any + Send + Sync>,
268 ) -> Option<Arc<dyn ExecutionPlan>> {
269 let work_table = state.downcast::<WorkTable>().ok()?;
271
272 if work_table.name != self.name {
273 return None; }
275
276 Some(Arc::new(Self {
277 name: self.name.clone(),
278 schema: Arc::clone(&self.schema),
279 projection: self.projection.clone(),
280 metrics: ExecutionPlanMetricsSet::new(),
281 work_table,
282 cache: Arc::clone(&self.cache),
283 }))
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use arrow::array::{ArrayRef, Int16Array, Int32Array, Int64Array};
291 use arrow_schema::{DataType, Field, Schema};
292 use datafusion_execution::memory_pool::{MemoryConsumer, UnboundedMemoryPool};
293 use futures::StreamExt;
294
295 #[test]
296 fn test_work_table() {
297 let work_table = WorkTable::new("test".into());
298 assert!(work_table.take().is_err());
300
301 let pool = Arc::new(UnboundedMemoryPool::default()) as _;
302 let reservation = MemoryConsumer::new("test_work_table").register(&pool);
303
304 let array: ArrayRef = Arc::new((0..5).collect::<Int32Array>());
306 let batch = RecordBatch::try_from_iter(vec![("col", array)]).unwrap();
307 reservation.try_grow(100).unwrap();
308 work_table.update(ReservedBatches::new(vec![batch.clone()], reservation));
309 let reserved_batches = work_table.take().unwrap();
311 assert_eq!(reserved_batches.batches, vec![batch.clone()]);
312
313 let memory_stream =
315 MemoryStream::try_new(reserved_batches.batches, batch.schema(), None)
316 .unwrap()
317 .with_reservation(reserved_batches.reservation);
318
319 assert_eq!(pool.reserved(), 100);
321
322 drop(memory_stream);
324 assert_eq!(pool.reserved(), 0);
325 }
326
327 #[tokio::test]
328 async fn test_work_table_exec() {
329 let schema = Arc::new(Schema::new(vec![
330 Field::new("a", DataType::Int64, false),
331 Field::new("b", DataType::Int32, false),
332 Field::new("c", DataType::Int16, false),
333 ]));
334 let work_table_exec =
335 WorkTableExec::new("wt".into(), Arc::clone(&schema), Some(vec![2, 1]))
336 .unwrap();
337
338 let work_table = Arc::new(WorkTable::new("wt".into()));
340 let work_table_exec = work_table_exec
341 .with_new_state(Arc::clone(&work_table) as _)
342 .unwrap();
343
344 let pool = Arc::new(UnboundedMemoryPool::default()) as _;
346 let reservation = MemoryConsumer::new("test_work_table").register(&pool);
347 let batch = RecordBatch::try_new(
348 Arc::clone(&schema),
349 vec![
350 Arc::new(Int64Array::from(vec![1, 2, 3, 4, 5])),
351 Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5])),
352 Arc::new(Int16Array::from(vec![1, 2, 3, 4, 5])),
353 ],
354 )
355 .unwrap();
356 work_table.update(ReservedBatches::new(vec![batch], reservation));
357
358 let returned_batch = work_table_exec
360 .execute(0, Arc::new(TaskContext::default()))
361 .unwrap()
362 .next()
363 .await
364 .unwrap()
365 .unwrap();
366 assert_eq!(
367 returned_batch,
368 RecordBatch::try_from_iter(vec![
369 ("c", Arc::new(Int16Array::from(vec![1, 2, 3, 4, 5])) as _),
370 ("b", Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5])) as _),
371 ])
372 .unwrap()
373 );
374 }
375}