Skip to content

Commit 733fe06

Browse files
committed
Add config to VortexSource to enable/disable predicate pushdown
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
1 parent f9d182f commit 733fe06

2 files changed

Lines changed: 79 additions & 10 deletions

File tree

vortex-datafusion/src/persistent/format.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ config_namespace! {
146146
///
147147
/// let factory = VortexFormatFactory::new().with_options(VortexTableOptions {
148148
/// projection_pushdown: true,
149+
/// predicate_pushdown: true,
149150
/// scan_concurrency: Some(8),
150151
/// ..Default::default()
151152
/// });
@@ -165,6 +166,12 @@ config_namespace! {
165166
/// the scan. When disabled, Vortex reads only the referenced columns and
166167
/// all expressions are evaluated after the scan.
167168
pub projection_pushdown: bool, default = false
169+
/// Whether to enable predicate pushdown into the underlying Vortex scan.
170+
///
171+
/// When enabled, supported filters are evaluated during the scan. When
172+
/// disabled, DataFusion evaluates filters after the scan, while
173+
/// `VortexSource` can still use the full predicate for file pruning.
174+
pub predicate_pushdown: bool, default = true
168175
/// The intra-partition scan concurrency, controlling the number of row splits to process
169176
/// concurrently per-thread within each file.
170177
///
@@ -198,6 +205,7 @@ impl Eq for VortexTableOptions {}
198205
///
199206
/// let factory = Arc::new(VortexFormatFactory::new().with_options(VortexTableOptions {
200207
/// projection_pushdown: true,
208+
/// predicate_pushdown: true,
201209
/// ..Default::default()
202210
/// }));
203211
///
@@ -263,6 +271,7 @@ impl VortexFormatFactory {
263271
///
264272
/// let factory = VortexFormatFactory::new().with_options(VortexTableOptions {
265273
/// projection_pushdown: true,
274+
/// predicate_pushdown: true,
266275
/// ..Default::default()
267276
/// });
268277
/// # let _ = factory;
@@ -617,14 +626,9 @@ impl FileFormat for VortexFormat {
617626
}
618627

619628
fn file_source(&self, table_schema: TableSchema) -> Arc<dyn FileSource> {
620-
let mut source = VortexSource::new(table_schema, self.session.clone())
621-
.with_projection_pushdown(self.opts.projection_pushdown);
622-
623-
if let Some(scan_concurrency) = self.opts.scan_concurrency {
624-
source = source.with_scan_concurrency(scan_concurrency);
625-
}
626-
627-
Arc::new(source) as _
629+
Arc::new(
630+
VortexSource::new(table_schema, self.session.clone()).with_options(self.opts.clone()),
631+
) as _
628632
}
629633
}
630634

@@ -682,7 +686,7 @@ mod tests {
682686
(c1 VARCHAR NOT NULL, c2 INT NOT NULL) \
683687
STORED AS vortex \
684688
LOCATION 'table/' \
685-
OPTIONS( footer_initial_read_size_bytes '12345', scan_concurrency '3' );",
689+
OPTIONS( footer_initial_read_size_bytes '12345', predicate_pushdown 'false', scan_concurrency '3' );",
686690
)
687691
.await?
688692
.collect()
@@ -699,4 +703,24 @@ mod tests {
699703
let format = VortexFormat::new_with_options(VortexSession::default(), opts);
700704
assert_eq!(format.options().footer_initial_read_size_bytes, 12345);
701705
}
706+
707+
#[test]
708+
fn format_plumbs_source_options() -> anyhow::Result<()> {
709+
let opts = VortexTableOptions {
710+
projection_pushdown: true,
711+
predicate_pushdown: false,
712+
scan_concurrency: Some(3),
713+
..Default::default()
714+
};
715+
let format = VortexFormat::new_with_options(VortexSession::default(), opts.clone());
716+
let table_schema = TableSchema::from_file_schema(Arc::new(Schema::empty()));
717+
718+
let source = format.file_source(table_schema);
719+
let source = source
720+
.downcast_ref::<VortexSource>()
721+
.ok_or_else(|| anyhow::anyhow!("expected VortexSource"))?;
722+
723+
assert_eq!(source.options(), &opts);
724+
Ok(())
725+
}
702726
}

vortex-datafusion/src/persistent/source.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ use crate::persistent::reader::VortexReaderFactory;
140140
/// - when enabled, the scan can evaluate a Vortex-native projection and leave
141141
/// only unsupported expressions for DataFusion.
142142
///
143+
/// Predicate handling depends on [`VortexTableOptions::predicate_pushdown`]:
144+
///
145+
/// - when disabled, `VortexSource` still keeps the full predicate for
146+
/// DataFusion file pruning, but reports filters as not pushed down so
147+
/// DataFusion evaluates them after the scan,
148+
/// - when enabled, supported filters are pushed into the Vortex scan.
149+
///
143150
/// # Observability
144151
///
145152
/// `VortexSource` owns a Vortex metrics registry for the lifetime of a physical
@@ -170,6 +177,7 @@ use crate::persistent::reader::VortexReaderFactory;
170177
/// [`VortexAccessPlan`]: crate::VortexAccessPlan
171178
/// [`FileMetadataCache`]: datafusion_execution::cache::cache_manager::FileMetadataCache
172179
/// [`VortexTableOptions::projection_pushdown`]: crate::VortexTableOptions::projection_pushdown
180+
/// [`VortexTableOptions::predicate_pushdown`]: crate::VortexTableOptions::predicate_pushdown
173181
/// [`VortexMetricsFinder`]: crate::metrics::VortexMetricsFinder
174182
#[derive(Clone)]
175183
pub struct VortexSource {
@@ -195,7 +203,7 @@ pub struct VortexSource {
195203
pub(crate) ordered: bool,
196204
vx_metrics_registry: Arc<dyn MetricsRegistry>,
197205
file_metadata_cache: Option<Arc<dyn FileMetadataCache>>,
198-
/// Whether to enable expression pushdown into the underlying Vortex scan.
206+
/// Options controlling scan planning and execution behavior.
199207
options: VortexTableOptions,
200208
}
201209

@@ -242,6 +250,15 @@ impl VortexSource {
242250
self
243251
}
244252

253+
/// Enables or disables Vortex-native predicate evaluation.
254+
///
255+
/// When disabled, DataFusion evaluates filters after the scan. The source
256+
/// still records the full predicate for file pruning.
257+
pub fn with_predicate_pushdown(mut self, enabled: bool) -> Self {
258+
self.options.predicate_pushdown = enabled;
259+
self
260+
}
261+
245262
/// Sets the [`ExpressionConvertor`] used to translate DataFusion expressions
246263
/// into Vortex expressions.
247264
///
@@ -452,6 +469,14 @@ impl FileSource for VortexSource {
452469
None => Some(conjunction(filters.clone())),
453470
};
454471

472+
if !source.options.predicate_pushdown {
473+
return Ok(FilterPushdownPropagation::with_parent_pushdown_result(vec![
474+
PushedDown::No;
475+
filters.len()
476+
])
477+
.with_updated_node(Arc::new(source) as _));
478+
}
479+
455480
let supported_filters = filters
456481
.into_iter()
457482
.map(|expr| {
@@ -685,4 +710,24 @@ mod tests {
685710
assert!(updated_source.vortex_predicate.is_some());
686711
Ok(())
687712
}
713+
714+
#[test]
715+
fn try_pushdown_filters_respects_disabled_predicate_pushdown() -> anyhow::Result<()> {
716+
let schema = Arc::new(Schema::new(vec![Field::new("name", DataType::Utf8, false)]));
717+
let source = sort_test_source(Arc::clone(&schema)).with_predicate_pushdown(false);
718+
let filter = octet_length_filter(&schema);
719+
720+
let result = source.try_pushdown_filters(vec![filter], &ConfigOptions::new())?;
721+
722+
assert!(matches!(result.filters.as_slice(), [PushedDown::No]));
723+
let updated_source = result
724+
.updated_node
725+
.ok_or_else(|| anyhow::anyhow!("expected updated VortexSource"))?
726+
.downcast_ref::<VortexSource>()
727+
.ok_or_else(|| anyhow::anyhow!("expected VortexSource"))?
728+
.clone();
729+
assert!(updated_source.full_predicate.is_some());
730+
assert!(updated_source.vortex_predicate.is_none());
731+
Ok(())
732+
}
688733
}

0 commit comments

Comments
 (0)