1use std::any::Any;
21use std::fmt;
22use std::fmt::Debug;
23use std::sync::Arc;
24
25use arrow::array::{ArrayRef, RecordBatch, UInt64Array};
26use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
27use datafusion_common::tree_node::TreeNodeRecursion;
28use datafusion_common::{Result, assert_eq_or_internal_err};
29use datafusion_execution::TaskContext;
30use datafusion_physical_expr::{Distribution, EquivalenceProperties, PhysicalExpr};
31use datafusion_physical_expr_common::sort_expr::{LexRequirement, OrderingRequirements};
32use datafusion_physical_plan::metrics::MetricsSet;
33use datafusion_physical_plan::stream::RecordBatchStreamAdapter;
34use datafusion_physical_plan::{
35 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan,
36 ExecutionPlanProperties, InputDistributionRequirements, Partitioning, PlanProperties,
37 ReplaceChildrenOptions, SendableRecordBatchStream, execute_input_stream,
38};
39
40use async_trait::async_trait;
41use datafusion_physical_plan::execution_plan::{EvaluationType, SchedulingType};
42use futures::StreamExt;
43
44#[async_trait]
50pub trait DataSink: Any + DisplayAs + Debug + Send + Sync {
51 fn metrics(&self) -> Option<MetricsSet> {
56 None
57 }
58
59 fn schema(&self) -> &SchemaRef;
61
62 async fn write_all(
71 &self,
72 data: SendableRecordBatchStream,
73 context: &Arc<TaskContext>,
74 ) -> Result<u64>;
75
76 #[cfg(feature = "proto")]
83 fn try_to_proto(
84 &self,
85 _exec: &DataSinkExec,
86 _ctx: &datafusion_physical_plan::proto::ExecutionPlanEncodeCtx<'_>,
87 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
88 Ok(None)
89 }
90}
91
92impl dyn DataSink {
93 pub fn is<T: DataSink>(&self) -> bool {
95 (self as &dyn Any).is::<T>()
96 }
97
98 pub fn downcast_ref<T: DataSink>(&self) -> Option<&T> {
100 (self as &dyn Any).downcast_ref()
101 }
102}
103
104#[derive(Clone)]
108pub struct DataSinkExec {
109 input: Arc<dyn ExecutionPlan>,
111 sink: Arc<dyn DataSink>,
113 count_schema: SchemaRef,
115 sort_order: Option<LexRequirement>,
117 cache: Arc<PlanProperties>,
118}
119
120impl Debug for DataSinkExec {
121 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122 write!(f, "DataSinkExec schema: {}", self.count_schema)
123 }
124}
125
126impl DataSinkExec {
127 pub fn new(
134 input: Arc<dyn ExecutionPlan>,
135 sink: Arc<dyn DataSink>,
136 sort_order: Option<LexRequirement>,
137 ) -> Self {
138 let count_schema = make_count_schema();
139 let cache = Self::create_schema(&input, count_schema);
140 Self {
141 input,
142 sink,
143 count_schema: make_count_schema(),
144 sort_order,
145 cache: Arc::new(cache),
146 }
147 }
148
149 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
151 &self.input
152 }
153
154 pub fn sink(&self) -> &dyn DataSink {
156 self.sink.as_ref()
157 }
158
159 pub fn sort_order(&self) -> &Option<LexRequirement> {
161 &self.sort_order
162 }
163
164 #[cfg(feature = "proto")]
166 pub fn encode_sort_order(
167 &self,
168 ctx: &datafusion_physical_plan::proto::ExecutionPlanEncodeCtx<'_>,
169 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalSortExprNodeCollection>>
170 {
171 use datafusion_physical_expr::PhysicalSortExpr;
172 use datafusion_proto_models::protobuf;
173
174 self.sort_order
175 .as_ref()
176 .map(|requirements| {
177 requirements
178 .iter()
179 .map(|requirement| {
180 let expr: PhysicalSortExpr = requirement.to_owned().into();
181 Ok(protobuf::PhysicalSortExprNode {
182 expr: Some(Box::new(ctx.encode_expr(&expr.expr)?)),
183 asc: !expr.options.descending,
184 nulls_first: expr.options.nulls_first,
185 })
186 })
187 .collect::<Result<Vec<_>>>()
188 .map(|physical_sort_expr_nodes| {
189 protobuf::PhysicalSortExprNodeCollection {
190 physical_sort_expr_nodes,
191 }
192 })
193 })
194 .transpose()
195 }
196
197 #[cfg(feature = "proto")]
199 pub fn decode_sort_order(
200 collection: Option<
201 &datafusion_proto_models::protobuf::PhysicalSortExprNodeCollection,
202 >,
203 ctx: &datafusion_physical_plan::proto::ExecutionPlanDecodeCtx<'_>,
204 schema: &Schema,
205 ) -> Result<Option<LexRequirement>> {
206 use arrow::compute::SortOptions;
207 use datafusion_physical_expr::PhysicalSortExpr;
208
209 let Some(collection) = collection else {
210 return Ok(None);
211 };
212 let sort_exprs = collection
213 .physical_sort_expr_nodes
214 .iter()
215 .map(|node| {
216 let expr = node.expr.as_ref().ok_or_else(|| {
217 datafusion_common::internal_datafusion_err!(
218 "Unexpected empty physical expression"
219 )
220 })?;
221 Ok(PhysicalSortExpr {
222 expr: ctx.decode_expr(expr, schema)?,
223 options: SortOptions {
224 descending: !node.asc,
225 nulls_first: node.nulls_first,
226 },
227 })
228 })
229 .collect::<Result<Vec<_>>>()?;
230 Ok(LexRequirement::new(sort_exprs.into_iter().map(Into::into)))
231 }
232
233 fn create_schema(
234 input: &Arc<dyn ExecutionPlan>,
235 schema: SchemaRef,
236 ) -> PlanProperties {
237 let eq_properties = EquivalenceProperties::new(schema);
238 PlanProperties::new(
239 eq_properties,
240 Partitioning::UnknownPartitioning(1),
241 input.pipeline_behavior(),
242 input.boundedness(),
243 )
244 .with_scheduling_type(SchedulingType::Cooperative)
245 .with_evaluation_type(EvaluationType::Eager)
246 }
247}
248
249impl DisplayAs for DataSinkExec {
250 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
251 match t {
252 DisplayFormatType::Default | DisplayFormatType::Verbose => {
253 write!(f, "DataSinkExec: sink=")?;
254 self.sink.fmt_as(t, f)
255 }
256 DisplayFormatType::TreeRender => self.sink().fmt_as(t, f),
257 }
258 }
259}
260
261impl ExecutionPlan for DataSinkExec {
262 fn name(&self) -> &'static str {
263 "DataSinkExec"
264 }
265
266 fn properties(&self) -> &Arc<PlanProperties> {
268 &self.cache
269 }
270
271 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
272 vec![false]
275 }
276
277 fn required_input_distribution(&self) -> Vec<Distribution> {
278 self.input_distribution_requirements().into_per_child()
279 }
280
281 fn input_distribution_requirements(&self) -> InputDistributionRequirements {
282 InputDistributionRequirements::new(vec![
285 Distribution::SinglePartition;
286 self.children().len()
287 ])
288 }
289
290 fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
291 vec![self.sort_order.as_ref().cloned().map(Into::into)]
294 }
295
296 fn maintains_input_order(&self) -> Vec<bool> {
297 vec![true]
302 }
303
304 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
305 vec![&self.input]
306 }
307
308 fn replace_children(
309 self: Arc<Self>,
310 children: Vec<Arc<dyn ExecutionPlan>>,
311 _: ReplaceChildrenOptions,
312 ) -> Result<Arc<dyn ExecutionPlan>> {
313 Ok(Arc::new(Self::new(
314 Arc::clone(&children[0]),
315 Arc::clone(&self.sink),
316 self.sort_order.clone(),
317 )))
318 }
319
320 fn with_new_children(
321 self: Arc<Self>,
322 children: Vec<Arc<dyn ExecutionPlan>>,
323 ) -> Result<Arc<dyn ExecutionPlan>> {
324 self.replace_children(
325 children,
326 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
327 )
328 }
329
330 fn apply_expressions(
331 &self,
332 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
333 ) -> Result<TreeNodeRecursion> {
334 Ok(TreeNodeRecursion::Continue)
335 }
336
337 fn execute(
340 &self,
341 partition: usize,
342 context: Arc<TaskContext>,
343 ) -> Result<SendableRecordBatchStream> {
344 assert_eq_or_internal_err!(
345 partition,
346 0,
347 "DataSinkExec can only be called on partition 0!"
348 );
349 let data = execute_input_stream(
350 Arc::clone(&self.input),
351 Arc::clone(self.sink.schema()),
352 0,
353 Arc::clone(&context),
354 )?;
355
356 let count_schema = Arc::clone(&self.count_schema);
357 let sink = Arc::clone(&self.sink);
358
359 let stream = futures::stream::once(async move {
360 sink.write_all(data, &context).await.map(make_count_batch)
361 })
362 .boxed();
363
364 Ok(Box::pin(RecordBatchStreamAdapter::new(
365 count_schema,
366 stream,
367 )))
368 }
369
370 fn metrics(&self) -> Option<MetricsSet> {
372 self.sink.metrics()
373 }
374
375 #[cfg(feature = "proto")]
377 fn try_to_proto(
378 &self,
379 ctx: &datafusion_physical_plan::proto::ExecutionPlanEncodeCtx<'_>,
380 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
381 self.sink().try_to_proto(self, ctx)
382 }
383}
384
385fn make_count_batch(count: u64) -> RecordBatch {
395 let array = Arc::new(UInt64Array::from(vec![count])) as ArrayRef;
396
397 RecordBatch::try_from_iter_with_nullable(vec![("count", array, false)]).unwrap()
398}
399
400fn make_count_schema() -> SchemaRef {
401 Arc::new(Schema::new(vec![Field::new(
403 "count",
404 DataType::UInt64,
405 false,
406 )]))
407}