Skip to main content

vortex_scan/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4#![deny(missing_docs)]
5
6//! The Vortex Scan API implements an abstract table scan interface that can be used to
7//! read data from various data sources.
8//!
9//! It supports arbitrary projection expressions, filter expressions, and limit pushdown as well
10//! as mechanisms for parallel and distributed execution via partitions.
11//!
12//! The API is currently under development and may change in future releases, however we hope to
13//! stabilize into stable C ABI for use within foreign language bindings.
14//!
15//! If you are looking to scan Vortex files or layouts, the Vortex implementation of the Scan API
16//! can be found in the `vortex-layout` crate.
17//!
18//! ## Open Issues
19//!
20//! * We probably want to make the DataSource serializable as well, so that we can share
21//!   source-level state with workers, separately from partition serialization.
22//! * We should add a way for the client to negotiate capabilities with the data source, for
23//!   example which encodings it knows about.
24
25pub mod planning;
26pub mod row_mask;
27pub mod selection;
28pub mod strict_sorted_buffer;
29
30use std::any::Any;
31use std::ops::Range;
32use std::sync::Arc;
33
34use async_trait::async_trait;
35use futures::stream::BoxStream;
36use selection::Selection;
37use vortex_array::dtype::DType;
38use vortex_array::dtype::FieldPath;
39use vortex_array::expr::Expression;
40use vortex_array::expr::root;
41use vortex_array::expr::stats::Precision;
42use vortex_array::stats::StatsSet;
43use vortex_array::stream::SendableArrayStream;
44use vortex_error::VortexResult;
45use vortex_error::vortex_bail;
46use vortex_session::VortexSession;
47
48/// A sendable stream of partitions.
49pub type PartitionStream = BoxStream<'static, VortexResult<PartitionRef>>;
50
51/// Opens a Vortex [`DataSource`] from a URI.
52///
53/// Configuration can be passed via the URI query parameters, similar to JDBC / ADBC.
54/// Providers can be registered with the [`VortexSession`] to support additional URI schemes.
55#[async_trait]
56pub trait DataSourceOpener: 'static {
57    /// Attempt to open a new data source from a URI.
58    async fn open(&self, uri: String, session: &VortexSession) -> VortexResult<DataSourceRef>;
59}
60
61/// Supports deserialization of a Vortex [`DataSource`] on a remote worker.
62#[async_trait]
63pub trait DataSourceRemote: 'static {
64    /// Attempt to deserialize the source.
65    fn deserialize_data_source(
66        &self,
67        data: &[u8],
68        session: &VortexSession,
69    ) -> VortexResult<DataSourceRef>;
70}
71
72/// A reference-counted data source.
73pub type DataSourceRef = Arc<dyn DataSource>;
74
75/// A data source represents a streamable dataset that can be scanned with projection and filter
76/// expressions. Each scan produces partitions that can be executed in parallel to read data. Each
77/// partition can be serialized for remote execution.
78///
79/// The DataSource may be used multiple times to create multiple scans, whereas each scan and each
80/// partition of a scan can only be consumed once.
81///
82/// Partitions have indices. Partition index is stable throughout DataSource's
83/// lifetime. For every scan requested on the DataSource, the index will stay
84/// the same.
85/// However, this means you should create another instance of a DataSource if
86/// your environment changes e.g. you have a glob and another file is added to
87/// the filesystem this glob references.
88/// See MultiFileDataSource in vortex-file/src/multi/mod.rs
89#[async_trait]
90pub trait DataSource: 'static + Send + Sync {
91    /// Returns the dtype of the source.
92    fn dtype(&self) -> &DType;
93
94    /// Returns an estimate of the row count of the un-filtered source.
95    fn row_count(&self) -> Precision<u64> {
96        Precision::Absent
97    }
98
99    /// Returns an estimate of the byte size of the un-filtered source.
100    fn byte_size(&self) -> Precision<u64> {
101        Precision::Absent
102    }
103
104    /// Serialize the [`DataSource`] to pass to a remote worker.
105    fn serialize(&self) -> VortexResult<Option<Vec<u8>>> {
106        Ok(None)
107    }
108
109    /// Deserialize a partition that was previously serialized from a compatible data source.
110    fn deserialize_partition(
111        &self,
112        data: &[u8],
113        session: &VortexSession,
114    ) -> VortexResult<PartitionRef> {
115        let _ = (data, session);
116        vortex_bail!("DataSource does not support deserialization")
117    }
118
119    /// Returns a scan over the source.
120    async fn scan(&self, scan_request: ScanRequest) -> VortexResult<DataSourceScanRef>;
121
122    /// Returns the statistics for a given field.
123    async fn field_statistics(&self, field_path: &FieldPath) -> VortexResult<StatsSet>;
124}
125
126/// A request to scan a data source.
127#[derive(Debug, Clone)]
128pub struct ScanRequest {
129    /// Projection expression. Defaults to `root()` which returns all columns.
130    pub projection: Expression,
131    /// Filter expression, `None` implies no filter.
132    pub filter: Option<Expression>,
133    /// The per-partition row range to read. Row range will be applied
134    /// over every partition you scan.
135    pub row_range: Option<Range<u64>>,
136    /// The per-partition row selection to read. Row selection will be applied
137    /// over every partition you scan.
138    pub selection: Selection,
139    /// Partition selection to scan, which allows readers to skip unwanted partitions.
140    pub partition_selection: Selection,
141    /// Partition range to scan, which allows readers to skip unwanted partitions.
142    pub partition_range: Option<Range<u64>>,
143    /// Whether the scan should preserve row order. If false, the scan may produce rows in any
144    /// order, for example to enable parallel execution across partitions.
145    pub ordered: bool,
146    /// Optional limit on the number of rows returned, applied after filtering and row selection.
147    ///
148    /// For an unordered scan the limit is global: partitions share one budget, so together they
149    /// return no more than `limit` rows. An ordered scan cannot share a budget whose reservation
150    /// order is completion order, so each partition applies the limit locally and the caller must
151    /// trim the concatenated result.
152    pub limit: Option<u64>,
153}
154
155impl Default for ScanRequest {
156    fn default() -> Self {
157        Self {
158            projection: root(),
159            filter: None,
160            row_range: None,
161            selection: Selection::default(),
162            partition_selection: Selection::default(),
163            ordered: false,
164            limit: None,
165            partition_range: None,
166        }
167    }
168}
169
170/// A boxed data source scan.
171pub type DataSourceScanRef = Box<dyn DataSourceScan>;
172
173/// A data source scan produces partitions that can be executed to read data from the source.
174pub trait DataSourceScan: 'static + Send {
175    /// The returned dtype of the scan.
176    fn dtype(&self) -> &DType;
177
178    /// Returns an estimate of the total number of partitions the scan will produce.
179    fn partition_count(&self) -> Precision<usize>;
180
181    /// Returns a stream of partitions to be processed.
182    fn partitions(self: Box<Self>) -> PartitionStream;
183}
184
185/// A reference-counted partition.
186pub type PartitionRef = Box<dyn Partition>;
187
188/// A partition represents a unit of work that can be executed to produce a stream of arrays.
189pub trait Partition: 'static + Send {
190    /// Downcast the partition to a concrete type.
191    fn as_any(&self) -> &dyn Any;
192
193    /// Some unique identifier for partition.
194    /// If you have an instance of a DataSource, the indices of emitted
195    /// partitions will stay stable for every scan in this DataSource.
196    fn index(&self) -> usize;
197
198    /// Returns an estimate of the row count for this partition.
199    fn row_count(&self) -> Precision<u64>;
200
201    /// Returns an estimate of the byte size for this partition.
202    fn byte_size(&self) -> Precision<u64>;
203
204    /// Serialize this partition for a remote worker.
205    fn serialize(&self) -> VortexResult<Option<Vec<u8>>> {
206        Ok(None)
207    }
208
209    /// Executes the partition, returning an array stream.
210    ///
211    /// This method must be fast. The returned stream should be lazy — all non-trivial work
212    /// (I/O, decoding, filtering) must be deferred to when the stream is polled. Expensive
213    /// operations should be spawned onto the runtime to enable parallel execution across
214    /// threads.
215    fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream>;
216}