Skip to main content

datafusion_datasource/
source.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! [`DataSource`] and [`DataSourceExec`]
19
20use std::any::Any;
21use std::fmt;
22use std::fmt::{Debug, Formatter};
23use std::sync::{Arc, OnceLock};
24
25use datafusion_physical_expr::projection::ProjectionExprs;
26use datafusion_physical_plan::execution_plan::{
27    Boundedness, EmissionType, SchedulingType,
28};
29use datafusion_physical_plan::metrics::SplitMetrics;
30use datafusion_physical_plan::metrics::{
31    BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet,
32};
33use datafusion_physical_plan::projection::ProjectionExec;
34use datafusion_physical_plan::stream::BatchSplitStream;
35use datafusion_physical_plan::{
36    DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
37};
38use itertools::Itertools;
39
40use crate::file::FileSource;
41use crate::file_scan_config::FileScanConfig;
42use datafusion_common::config::ConfigOptions;
43use datafusion_common::{Constraints, Result, Statistics};
44use datafusion_execution::{SendableRecordBatchStream, TaskContext};
45use datafusion_physical_expr::{EquivalenceProperties, Partitioning, PhysicalExpr};
46use datafusion_physical_expr_common::sort_expr::{LexOrdering, PhysicalSortExpr};
47use datafusion_physical_plan::SortOrderPushdownResult;
48use datafusion_physical_plan::filter_pushdown::{
49    ChildPushdownResult, FilterPushdownPhase, FilterPushdownPropagation, PushedDown,
50};
51
52/// A source of data, typically a list of files or memory
53///
54/// This trait provides common behaviors for abstract sources of data. It has
55/// two common implementations:
56///
57/// 1. [`FileScanConfig`]: lists of files
58/// 2. [`MemorySourceConfig`]: in memory list of `RecordBatch`
59///
60/// File format specific behaviors are defined by [`FileSource`]
61///
62/// # See Also
63/// * [`FileSource`] for file format specific implementations (Parquet, Json, etc)
64/// * [`DataSourceExec`]: The [`ExecutionPlan`] that reads from a `DataSource`
65///
66/// # Notes
67///
68/// Requires `Debug` to assist debugging
69///
70/// [`FileScanConfig`]: https://docs.rs/datafusion/latest/datafusion/datasource/physical_plan/struct.FileScanConfig.html
71/// [`MemorySourceConfig`]: https://docs.rs/datafusion/latest/datafusion/datasource/memory/struct.MemorySourceConfig.html
72/// [`FileSource`]: crate::file::FileSource
73/// [`FileFormat``]: https://docs.rs/datafusion/latest/datafusion/datasource/file_format/index.html
74/// [`TableProvider`]: https://docs.rs/datafusion/latest/datafusion/catalog/trait.TableProvider.html
75///
76/// The following diagram shows how DataSource, FileSource, and DataSourceExec are related
77/// ```text
78///                       ┌─────────────────────┐                              -----► execute path
79///                       │                     │                              ┄┄┄┄┄► init path
80///                       │   DataSourceExec    │
81///                       │                     │
82///                       └───────▲─────────────┘
83///                               ┊  │
84///                               ┊  │
85///                       ┌──────────▼──────────┐                            ┌──────────-──────────┐
86///                       │                     │                            |                     |
87///                       │  DataSource(trait)  │                            | TableProvider(trait)|
88///                       │                     │                            |                     |
89///                       └───────▲─────────────┘                            └─────────────────────┘
90///                               ┊  │                                                  ┊
91///               ┌───────────────┿──┴────────────────┐                                 ┊
92///               |   ┌┄┄┄┄┄┄┄┄┄┄┄┘                   |                                 ┊
93///               |   ┊                               |                                 ┊
94///    ┌──────────▼──────────┐             ┌──────────▼──────────┐                      ┊
95///    │                     │             │                     │           ┌──────────▼──────────┐
96///    │   FileScanConfig    │             │ MemorySourceConfig  │           |                     |
97///    │                     │             │                     │           |  FileFormat(trait)  |
98///    └──────────────▲──────┘             └─────────────────────┘           |                     |
99///               │   ┊                                                      └─────────────────────┘
100///               │   ┊                                                                 ┊
101///               │   ┊                                                                 ┊
102///    ┌──────────▼──────────┐                                               ┌──────────▼──────────┐
103///    │                     │                                               │     ArrowSource     │
104///    │ FileSource(trait)   ◄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄│          ...        │
105///    │                     │                                               │    ParquetSource    │
106///    └─────────────────────┘                                               └─────────────────────┘
107///               │
108///               │
109///               │
110///               │
111///    ┌──────────▼──────────┐
112///    │     ArrowSource     │
113///    │          ...        │
114///    │    ParquetSource    │
115///    └─────────────────────┘
116///               |
117/// FileOpener (called by FileStream)
118///               │
119///    ┌──────────▼──────────┐
120///    │                     │
121///    │     RecordBatch     │
122///    │                     │
123///    └─────────────────────┘
124/// ```
125pub trait DataSource: Any + Send + Sync + Debug {
126    /// Open the specified output partition and return its stream of
127    /// [`RecordBatch`]es.
128    ///
129    /// This should be used by data sources that do not need any sibling
130    /// coordination. Data sources that want to use per-execution shared state
131    /// (for example, to reorder work across partitions at runtime) should
132    /// implement [`Self::open_with_args`] instead.
133    ///
134    /// [`RecordBatch`]: arrow::record_batch::RecordBatch
135    fn open(
136        &self,
137        partition: usize,
138        context: Arc<TaskContext>,
139    ) -> Result<SendableRecordBatchStream>;
140
141    /// Format this source for display in explain plans
142    fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result;
143
144    /// Return a copy of this DataSource with a new partitioning scheme.
145    ///
146    /// Returns `Ok(None)` (the default) if the partitioning cannot be changed.
147    /// Refer to [`ExecutionPlan::repartitioned`] for details on when None should be returned.
148    ///
149    /// Repartitioning should not change the output ordering, if this ordering exists.
150    /// Refer to [`MemorySourceConfig::repartition_preserving_order`](crate::memory::MemorySourceConfig)
151    /// and the FileSource's
152    /// [`FileGroupPartitioner::repartition_file_groups`](crate::file_groups::FileGroupPartitioner::repartition_file_groups)
153    /// for examples.
154    fn repartitioned(
155        &self,
156        _target_partitions: usize,
157        _repartition_file_min_size: usize,
158        _output_ordering: Option<LexOrdering>,
159    ) -> Result<Option<Arc<dyn DataSource>>> {
160        Ok(None)
161    }
162
163    fn output_partitioning(&self) -> Partitioning;
164    fn eq_properties(&self) -> EquivalenceProperties;
165    fn scheduling_type(&self) -> SchedulingType {
166        SchedulingType::NonCooperative
167    }
168
169    /// Returns statistics for a specific partition, or aggregate statistics
170    /// across all partitions if `partition` is `None`.
171    fn partition_statistics(&self, partition: Option<usize>) -> Result<Arc<Statistics>>;
172
173    /// Return a copy of this DataSource with a new fetch limit
174    fn with_fetch(&self, _limit: Option<usize>) -> Option<Arc<dyn DataSource>>;
175    fn fetch(&self) -> Option<usize>;
176    fn metrics(&self) -> ExecutionPlanMetricsSet {
177        ExecutionPlanMetricsSet::new()
178    }
179    fn try_swapping_with_projection(
180        &self,
181        _projection: &ProjectionExprs,
182    ) -> Result<Option<Arc<dyn DataSource>>>;
183
184    /// Try to push down filters into this DataSource.
185    ///
186    /// These filters are in terms of the output schema of this DataSource (e.g.
187    /// [`Self::eq_properties`] and output of any projections pushed into the
188    /// source), not the original table schema.
189    ///
190    /// See [`ExecutionPlan::handle_child_pushdown_result`] for more details.
191    ///
192    /// [`ExecutionPlan::handle_child_pushdown_result`]: datafusion_physical_plan::ExecutionPlan::handle_child_pushdown_result
193    fn try_pushdown_filters(
194        &self,
195        filters: Vec<Arc<dyn PhysicalExpr>>,
196        _config: &ConfigOptions,
197    ) -> Result<FilterPushdownPropagation<Arc<dyn DataSource>>> {
198        Ok(FilterPushdownPropagation::with_parent_pushdown_result(
199            vec![PushedDown::No; filters.len()],
200        ))
201    }
202
203    /// Try to create a new DataSource that produces data in the specified sort order.
204    ///
205    /// # Arguments
206    /// * `order` - The desired output ordering
207    ///
208    /// # Returns
209    /// * `Ok(SortOrderPushdownResult::Exact { .. })` - Created a source that guarantees exact ordering
210    /// * `Ok(SortOrderPushdownResult::Inexact { .. })` - Created a source optimized for the ordering
211    /// * `Ok(SortOrderPushdownResult::Unsupported)` - Cannot optimize for this ordering
212    /// * `Err(e)` - Error occurred
213    ///
214    /// Default implementation returns `Unsupported`.
215    fn try_pushdown_sort(
216        &self,
217        _order: &[PhysicalSortExpr],
218    ) -> Result<SortOrderPushdownResult<Arc<dyn DataSource>>> {
219        Ok(SortOrderPushdownResult::Unsupported)
220    }
221
222    /// Returns a variant of this `DataSource` that is aware of order-sensitivity.
223    fn with_preserve_order(&self, _preserve_order: bool) -> Option<Arc<dyn DataSource>> {
224        None
225    }
226
227    /// Injects arbitrary run-time state into this DataSource, returning a new instance
228    /// that incorporates that state *if* it is relevant to the concrete DataSource implementation.
229    ///
230    /// This is a generic entry point: the `state` can be any type wrapped in
231    /// `Arc<dyn Any + Send + Sync>`.  A data source that cares about the state should
232    /// down-cast it to the concrete type it expects and, if successful, return a
233    /// modified copy of itself that captures the provided value.  If the state is
234    /// not applicable, the default behaviour is to return `None` so that parent
235    /// nodes can continue propagating the attempt further down the plan tree.
236    fn with_new_state(
237        &self,
238        _state: Arc<dyn Any + Send + Sync>,
239    ) -> Option<Arc<dyn DataSource>> {
240        None
241    }
242
243    /// Create per execution state to share across sibling instances of this
244    /// data source during one execution.
245    ///
246    /// `config` is the session configuration, so implementations can honor
247    /// options that disable sibling sharing (returning `None`) for consumers
248    /// that cannot poll all partitions in one process.
249    ///
250    /// Returns `None` (the default) if this data source has
251    /// no sibling-shared execution state.
252    fn create_sibling_state(
253        &self,
254        _config: &ConfigOptions,
255    ) -> Option<Arc<dyn Any + Send + Sync>> {
256        None
257    }
258
259    /// Open a partition using optional sibling-shared execution state.
260    ///
261    /// The default implementation ignores the additional state and delegates to
262    /// [`Self::open`].
263    fn open_with_args(&self, args: OpenArgs) -> Result<SendableRecordBatchStream> {
264        self.open(args.partition, args.context)
265    }
266}
267
268/// Arguments for [`DataSource::open_with_args`]
269#[derive(Debug, Clone)]
270pub struct OpenArgs {
271    /// Which partition to open
272    pub partition: usize,
273    /// The task context for execution
274    pub context: Arc<TaskContext>,
275    /// Optional sibling-shared execution state, see
276    /// [`DataSource::create_sibling_state`] for details.
277    pub sibling_state: Option<Arc<dyn Any + Send + Sync>>,
278}
279
280impl OpenArgs {
281    /// Create a new OpenArgs with required arguments
282    pub fn new(partition: usize, context: Arc<TaskContext>) -> Self {
283        Self {
284            partition,
285            context,
286            sibling_state: None,
287        }
288    }
289
290    /// Set sibling shared state
291    pub fn with_shared_state(
292        mut self,
293        sibling_state: Option<Arc<dyn Any + Send + Sync>>,
294    ) -> Self {
295        self.sibling_state = sibling_state;
296        self
297    }
298}
299
300impl dyn DataSource {
301    pub fn is<T: DataSource>(&self) -> bool {
302        (self as &dyn Any).is::<T>()
303    }
304
305    pub fn downcast_ref<T: DataSource>(&self) -> Option<&T> {
306        (self as &dyn Any).downcast_ref()
307    }
308}
309
310/// [`ExecutionPlan`] that reads one or more files
311///
312/// `DataSourceExec` implements common functionality such as applying
313/// projections, and caching plan properties.
314///
315/// The [`DataSource`] describes where to find the data for this data source
316/// (for example in files or what in memory partitions).
317///
318/// For file based [`DataSource`]s, format specific behavior is implemented in
319/// the [`FileSource`] trait.
320///
321/// [`FileSource`]: crate::file::FileSource
322#[derive(Clone, Debug)]
323pub struct DataSourceExec {
324    /// The source of the data -- for example, `FileScanConfig` or `MemorySourceConfig`
325    data_source: Arc<dyn DataSource>,
326    /// Cached plan properties such as sort order
327    cache: Arc<PlanProperties>,
328    /// Per execution state shared across partitions of this plan.
329    ///
330    /// Created by [`DataSource::create_sibling_state`]
331    /// and then passed to
332    /// [`DataSource::open_with_args`].
333    execution_state: Arc<OnceLock<Option<Arc<dyn Any + Send + Sync>>>>,
334}
335
336impl DisplayAs for DataSourceExec {
337    fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result {
338        match t {
339            DisplayFormatType::Default | DisplayFormatType::Verbose => {
340                write!(f, "DataSourceExec: ")?;
341            }
342            DisplayFormatType::TreeRender => {}
343        }
344        self.data_source.fmt_as(t, f)
345    }
346}
347
348impl ExecutionPlan for DataSourceExec {
349    fn name(&self) -> &'static str {
350        "DataSourceExec"
351    }
352
353    fn properties(&self) -> &Arc<PlanProperties> {
354        &self.cache
355    }
356
357    fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
358        Vec::new()
359    }
360
361    fn with_new_children(
362        self: Arc<Self>,
363        _: Vec<Arc<dyn ExecutionPlan>>,
364    ) -> Result<Arc<dyn ExecutionPlan>> {
365        Ok(self)
366    }
367
368    /// Implementation of [`ExecutionPlan::repartitioned`] which relies upon the inner [`DataSource::repartitioned`].
369    ///
370    /// If the data source does not support changing its partitioning, returns `Ok(None)` (the default). Refer
371    /// to [`ExecutionPlan::repartitioned`] for more details.
372    fn repartitioned(
373        &self,
374        target_partitions: usize,
375        config: &ConfigOptions,
376    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
377        let data_source = self.data_source.repartitioned(
378            target_partitions,
379            config.optimizer.repartition_file_min_size,
380            self.properties().eq_properties.output_ordering(),
381        )?;
382
383        Ok(data_source.map(|source| {
384            let output_partitioning = source.output_partitioning();
385            let plan = self
386                .clone()
387                .with_data_source(source)
388                // Changing source partitioning may invalidate output partitioning. Update it also
389                .with_partitioning(output_partitioning);
390            Arc::new(plan) as _
391        }))
392    }
393
394    fn execute(
395        &self,
396        partition: usize,
397        context: Arc<TaskContext>,
398    ) -> Result<SendableRecordBatchStream> {
399        let shared_state = self
400            .execution_state
401            .get_or_init(|| {
402                self.data_source
403                    .create_sibling_state(context.session_config().options())
404            })
405            .clone();
406        let args = OpenArgs::new(partition, Arc::clone(&context))
407            .with_shared_state(shared_state);
408        let stream = self.data_source.open_with_args(args)?;
409        let batch_size = context.session_config().batch_size();
410
411        log::debug!(
412            "Batch splitting enabled for partition {partition}: batch_size={batch_size}"
413        );
414        let metrics = self.data_source.metrics();
415        let split_metrics = SplitMetrics::new(&metrics, partition);
416        Ok(Box::pin(BatchSplitStream::new(
417            stream,
418            batch_size,
419            split_metrics,
420        )))
421    }
422
423    fn metrics(&self) -> Option<MetricsSet> {
424        let mut metrics = self.data_source.metrics().clone_inner();
425
426        // Add `output_rows_skew` metric to the metrics set.
427        // Done here because it's a derived metric from output_rows metric.
428        if let Some(file_scan_config) = self.data_source.downcast_ref::<FileScanConfig>()
429            && file_scan_config.file_source().file_type() == "parquet"
430            && let Some(output_rows_skew) =
431                BaselineMetrics::output_rows_skew_metric(&metrics)
432        {
433            metrics.push(output_rows_skew);
434        }
435
436        Some(metrics)
437    }
438
439    fn partition_statistics(&self, partition: Option<usize>) -> Result<Arc<Statistics>> {
440        self.data_source.partition_statistics(partition)
441    }
442
443    fn with_fetch(&self, limit: Option<usize>) -> Option<Arc<dyn ExecutionPlan>> {
444        let data_source = self.data_source.with_fetch(limit)?;
445        let cache = Arc::clone(&self.cache);
446        let execution_state = Arc::new(OnceLock::new());
447
448        Some(Arc::new(Self {
449            data_source,
450            cache,
451            execution_state,
452        }))
453    }
454
455    fn fetch(&self) -> Option<usize> {
456        self.data_source.fetch()
457    }
458
459    fn try_swapping_with_projection(
460        &self,
461        projection: &ProjectionExec,
462    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
463        match self
464            .data_source
465            .try_swapping_with_projection(projection.projection_expr())?
466        {
467            Some(new_data_source) => {
468                Ok(Some(Arc::new(DataSourceExec::new(new_data_source))))
469            }
470            None => Ok(None),
471        }
472    }
473
474    fn handle_child_pushdown_result(
475        &self,
476        _phase: FilterPushdownPhase,
477        child_pushdown_result: ChildPushdownResult,
478        config: &ConfigOptions,
479    ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
480        // Push any remaining filters into our data source
481        let parent_filters = child_pushdown_result
482            .parent_filters
483            .into_iter()
484            .map(|f| f.filter)
485            .collect_vec();
486        let res = self
487            .data_source
488            .try_pushdown_filters(parent_filters, config)?;
489        match res.updated_node {
490            Some(data_source) => {
491                let mut new_node = self.clone();
492                new_node.data_source = data_source;
493                // Re-compute properties since we have new filters which will impact equivalence info
494                new_node.cache =
495                    Arc::new(Self::compute_properties(&new_node.data_source));
496
497                Ok(FilterPushdownPropagation {
498                    filters: res.filters,
499                    updated_node: Some(Arc::new(new_node)),
500                })
501            }
502            None => Ok(FilterPushdownPropagation {
503                filters: res.filters,
504                updated_node: None,
505            }),
506        }
507    }
508
509    fn try_pushdown_sort(
510        &self,
511        order: &[PhysicalSortExpr],
512    ) -> Result<SortOrderPushdownResult<Arc<dyn ExecutionPlan>>> {
513        // Delegate to the data source and wrap result with DataSourceExec
514        self.data_source
515            .try_pushdown_sort(order)?
516            .try_map(|new_data_source| {
517                let new_exec = self.clone().with_data_source(new_data_source);
518                Ok(Arc::new(new_exec) as Arc<dyn ExecutionPlan>)
519            })
520    }
521
522    fn with_preserve_order(
523        &self,
524        preserve_order: bool,
525    ) -> Option<Arc<dyn ExecutionPlan>> {
526        self.data_source
527            .with_preserve_order(preserve_order)
528            .map(|new_data_source| {
529                Arc::new(self.clone().with_data_source(new_data_source))
530                    as Arc<dyn ExecutionPlan>
531            })
532    }
533
534    fn with_new_state(
535        &self,
536        state: Arc<dyn Any + Send + Sync>,
537    ) -> Option<Arc<dyn ExecutionPlan>> {
538        self.data_source
539            .with_new_state(state)
540            .map(|new_data_source| {
541                Arc::new(self.clone().with_data_source(new_data_source))
542                    as Arc<dyn ExecutionPlan>
543            })
544    }
545
546    fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
547        let mut new_exec = Arc::unwrap_or_clone(self);
548        new_exec.execution_state = Arc::new(OnceLock::new());
549        Ok(Arc::new(new_exec))
550    }
551}
552
553impl DataSourceExec {
554    pub fn from_data_source(data_source: impl DataSource + 'static) -> Arc<Self> {
555        Arc::new(Self::new(Arc::new(data_source)))
556    }
557
558    // Default constructor for `DataSourceExec`, setting the `cooperative` flag to `true`.
559    pub fn new(data_source: Arc<dyn DataSource>) -> Self {
560        let cache = Self::compute_properties(&data_source);
561        Self {
562            data_source,
563            cache: Arc::new(cache),
564            execution_state: Arc::new(OnceLock::new()),
565        }
566    }
567
568    /// Return the source object
569    pub fn data_source(&self) -> &Arc<dyn DataSource> {
570        &self.data_source
571    }
572
573    pub fn with_data_source(mut self, data_source: Arc<dyn DataSource>) -> Self {
574        self.cache = Arc::new(Self::compute_properties(&data_source));
575        self.data_source = data_source;
576        self.execution_state = Arc::new(OnceLock::new());
577        self
578    }
579
580    /// Assign constraints
581    pub fn with_constraints(mut self, constraints: Constraints) -> Self {
582        Arc::make_mut(&mut self.cache).set_constraints(constraints);
583        self
584    }
585
586    /// Assign output partitioning
587    pub fn with_partitioning(mut self, partitioning: Partitioning) -> Self {
588        Arc::make_mut(&mut self.cache).partitioning = partitioning;
589        self
590    }
591
592    fn compute_properties(data_source: &Arc<dyn DataSource>) -> PlanProperties {
593        PlanProperties::new(
594            data_source.eq_properties(),
595            data_source.output_partitioning(),
596            EmissionType::Incremental,
597            Boundedness::Bounded,
598        )
599        .with_scheduling_type(data_source.scheduling_type())
600    }
601
602    /// Downcast the `DataSourceExec`'s `data_source` to a specific file source
603    ///
604    /// Returns `None` if
605    /// 1. the datasource is not scanning files (`FileScanConfig`)
606    /// 2. The [`FileScanConfig::file_source`] is not of type `T`
607    pub fn downcast_to_file_source<T: FileSource>(
608        &self,
609    ) -> Option<(&FileScanConfig, &T)> {
610        self.data_source()
611            .downcast_ref::<FileScanConfig>()
612            .and_then(|file_scan_conf| {
613                file_scan_conf
614                    .file_source()
615                    .downcast_ref::<T>()
616                    .map(|source| (file_scan_conf, source))
617            })
618    }
619}
620
621/// Create a new `DataSourceExec` from a `DataSource`
622impl<S> From<S> for DataSourceExec
623where
624    S: DataSource + 'static,
625{
626    fn from(source: S) -> Self {
627        Self::new(Arc::new(source))
628    }
629}