This is an automated email from the ASF dual-hosted git repository.
agrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git
The following commit(s) were added to refs/heads/main by this push:
new 3f826a3e perf: Remove redundant copying of batches after FilterExec
(#835)
3f826a3e is described below
commit 3f826a3e19c27aaa81eb1f67d392a24244d902f5
Author: Andy Grove <[email protected]>
AuthorDate: Fri Aug 16 16:42:43 2024 -0600
perf: Remove redundant copying of batches after FilterExec (#835)
* Use custom FilterExec that always uses take with a selection vector
* Remove CopyExec around FilterExec
* remove CopyExec on FilterExec inputs to joins
* remove copy before sort in some cases
* add comments
* cargo fmt
* bug fix: check for null when building selection vector
* revert
* use arrow kernel
* remove unused imports
* add criterion benchmark
* address initial feedback
* add ASF header
* fix missing imports
* Update native/core/src/execution/operators/filter.rs
Co-authored-by: Liang-Chi Hsieh <[email protected]>
---------
Co-authored-by: Liang-Chi Hsieh <[email protected]>
---
native/Cargo.lock | 1 +
native/Cargo.toml | 1 +
native/core/Cargo.toml | 5 +
native/core/benches/filter.rs | 111 ++++++
native/core/src/execution/datafusion/planner.rs | 8 +-
native/core/src/execution/operators/filter.rs | 452 ++++++++++++++++++++++++
native/core/src/execution/operators/mod.rs | 3 +
7 files changed, 575 insertions(+), 6 deletions(-)
diff --git a/native/Cargo.lock b/native/Cargo.lock
index 5d8dce28..27bc3828 100644
--- a/native/Cargo.lock
+++ b/native/Cargo.lock
@@ -881,6 +881,7 @@ dependencies = [
"datafusion-comet-proto",
"datafusion-comet-spark-expr",
"datafusion-common",
+ "datafusion-execution",
"datafusion-expr",
"datafusion-functions-nested",
"datafusion-physical-expr",
diff --git a/native/Cargo.toml b/native/Cargo.toml
index a41934fe..9977ceec 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -44,6 +44,7 @@ datafusion = { default-features = false, git =
"https://github.com/apache/datafu
datafusion-functions = { git = "https://github.com/apache/datafusion.git", rev
= "41.0.0-rc1", features = ["crypto_expressions"] }
datafusion-functions-nested = { git =
"https://github.com/apache/datafusion.git", rev = "41.0.0-rc1",
default-features = false }
datafusion-expr = { git = "https://github.com/apache/datafusion.git", rev =
"41.0.0-rc1", default-features = false }
+datafusion-execution = { git = "https://github.com/apache/datafusion.git", rev
= "41.0.0-rc1", default-features = false }
datafusion-physical-plan = { git = "https://github.com/apache/datafusion.git",
rev = "41.0.0-rc1", default-features = false }
datafusion-physical-expr-common = { git =
"https://github.com/apache/datafusion.git", rev = "41.0.0-rc1",
default-features = false }
datafusion-physical-expr = { git = "https://github.com/apache/datafusion.git",
rev = "41.0.0-rc1", default-features = false }
diff --git a/native/core/Cargo.toml b/native/core/Cargo.toml
index 55c46e1d..13a6a369 100644
--- a/native/core/Cargo.toml
+++ b/native/core/Cargo.toml
@@ -69,6 +69,7 @@ datafusion-common = { workspace = true }
datafusion = { workspace = true }
datafusion-functions-nested = { workspace = true }
datafusion-expr = { workspace = true }
+datafusion-execution = { workspace = true }
datafusion-physical-expr-common = { workspace = true }
datafusion-physical-expr = { workspace = true }
once_cell = "1.18.0"
@@ -118,3 +119,7 @@ harness = false
[[bench]]
name = "parquet_decode"
harness = false
+
+[[bench]]
+name = "filter"
+harness = false
diff --git a/native/core/benches/filter.rs b/native/core/benches/filter.rs
new file mode 100644
index 00000000..10ad5a9e
--- /dev/null
+++ b/native/core/benches/filter.rs
@@ -0,0 +1,111 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.use arrow::array::{ArrayRef, BooleanBuilder,
Int32Builder, RecordBatch, StringBuilder};
+
+use arrow::compute::filter_record_batch;
+use arrow::datatypes::{DataType, Field, Schema};
+use arrow_array::builder::{BooleanBuilder, Int32Builder, StringBuilder};
+use arrow_array::{ArrayRef, RecordBatch};
+use comet::execution::operators::comet_filter_record_batch;
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use std::sync::Arc;
+use std::time::Duration;
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let mut group = c.benchmark_group("filter");
+
+ let num_rows = 8192;
+ let num_int_cols = 4;
+ let num_string_cols = 4;
+
+ let batch = create_record_batch(num_rows, num_int_cols, num_string_cols);
+
+ // create some different predicates
+ let mut predicate_select_few = BooleanBuilder::with_capacity(num_rows);
+ let mut predicate_select_many = BooleanBuilder::with_capacity(num_rows);
+ let mut predicate_select_all = BooleanBuilder::with_capacity(num_rows);
+ for i in 0..num_rows {
+ predicate_select_few.append_value(i % 10 == 0);
+ predicate_select_many.append_value(i % 10 > 0);
+ predicate_select_all.append_value(true);
+ }
+ let predicate_select_few = predicate_select_few.finish();
+ let predicate_select_many = predicate_select_many.finish();
+ let predicate_select_all = predicate_select_all.finish();
+
+ // baseline uses Arrow's filter_record_batch method
+ group.bench_function("arrow_filter_record_batch - few rows selected", |b| {
+ b.iter(|| filter_record_batch(black_box(&batch),
black_box(&predicate_select_few)))
+ });
+ group.bench_function("arrow_filter_record_batch - many rows selected", |b|
{
+ b.iter(|| filter_record_batch(black_box(&batch),
black_box(&predicate_select_many)))
+ });
+ group.bench_function("arrow_filter_record_batch - all rows selected", |b| {
+ b.iter(|| filter_record_batch(black_box(&batch),
black_box(&predicate_select_all)))
+ });
+
+ group.bench_function("comet_filter_record_batch - few rows selected", |b| {
+ b.iter(|| comet_filter_record_batch(black_box(&batch),
black_box(&predicate_select_few)))
+ });
+ group.bench_function("comet_filter_record_batch - many rows selected", |b|
{
+ b.iter(|| comet_filter_record_batch(black_box(&batch),
black_box(&predicate_select_many)))
+ });
+ group.bench_function("comet_filter_record_batch - all rows selected", |b| {
+ b.iter(|| comet_filter_record_batch(black_box(&batch),
black_box(&predicate_select_all)))
+ });
+
+ group.finish();
+}
+
+fn create_record_batch(num_rows: usize, num_int_cols: i32, num_string_cols:
i32) -> RecordBatch {
+ let mut int32_builder = Int32Builder::with_capacity(num_rows);
+ let mut string_builder = StringBuilder::with_capacity(num_rows, num_rows *
32);
+ for i in 0..num_rows {
+ int32_builder.append_value(i as i32);
+ string_builder.append_value(format!("this is string #{i}"));
+ }
+ let int32_array = Arc::new(int32_builder.finish());
+ let string_array = Arc::new(string_builder.finish());
+
+ let mut fields = vec![];
+ let mut columns: Vec<ArrayRef> = vec![];
+ let mut i = 0;
+ for _ in 0..num_int_cols {
+ fields.push(Field::new(format!("c{i}"), DataType::Int32, false));
+ columns.push(int32_array.clone()); // note this is just copying a
reference to the array
+ i += 1;
+ }
+ for _ in 0..num_string_cols {
+ fields.push(Field::new(format!("c{i}"), DataType::Utf8, false));
+ columns.push(string_array.clone()); // note this is just copying a
reference to the array
+ i += 1;
+ }
+ let schema = Schema::new(fields);
+ RecordBatch::try_new(Arc::new(schema), columns).unwrap()
+}
+
+fn config() -> Criterion {
+ Criterion::default()
+ .measurement_time(Duration::from_millis(500))
+ .warm_up_time(Duration::from_millis(500))
+}
+
+criterion_group! {
+ name = benches;
+ config = config();
+ targets = criterion_benchmark
+}
+criterion_main!(benches);
diff --git a/native/core/src/execution/datafusion/planner.rs
b/native/core/src/execution/datafusion/planner.rs
index 122a24ed..24ea4142 100644
--- a/native/core/src/execution/datafusion/planner.rs
+++ b/native/core/src/execution/datafusion/planner.rs
@@ -19,7 +19,7 @@
use super::expressions::EvalMode;
use
crate::execution::datafusion::expressions::comet_scalar_funcs::create_comet_physical_fun;
-use crate::execution::operators::CopyMode;
+use crate::execution::operators::{CopyMode, FilterExec};
use crate::{
errors::ExpressionError,
execution::{
@@ -73,7 +73,6 @@ use datafusion::{
physical_optimizer::join_selection::swap_hash_join,
physical_plan::{
aggregates::{AggregateMode as DFAggregateMode, PhysicalGroupBy},
- filter::FilterExec,
joins::{utils::JoinFilter, HashJoinExec, PartitionMode,
SortMergeJoinExec},
limit::LocalLimitExec,
projection::ProjectionExec,
@@ -1780,10 +1779,7 @@ impl From<ExpressionError> for DataFusionError {
/// modification. This is used to determine if we need to copy the input batch
to avoid
/// data corruption from reusing the input batch.
fn can_reuse_input_batch(op: &Arc<dyn ExecutionPlan>) -> bool {
- if op.as_any().is::<ProjectionExec>()
- || op.as_any().is::<LocalLimitExec>()
- || op.as_any().is::<FilterExec>()
- {
+ if op.as_any().is::<ProjectionExec>() ||
op.as_any().is::<LocalLimitExec>() {
can_reuse_input_batch(op.children()[0])
} else {
op.as_any().is::<ScanExec>()
diff --git a/native/core/src/execution/operators/filter.rs
b/native/core/src/execution/operators/filter.rs
new file mode 100644
index 00000000..902529ba
--- /dev/null
+++ b/native/core/src/execution/operators/filter.rs
@@ -0,0 +1,452 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::any::Any;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{ready, Context, Poll};
+
+use datafusion::physical_plan::{
+ metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet},
+ ColumnStatistics, DisplayAs, DisplayFormatType, ExecutionPlan,
ExecutionPlanProperties,
+ PlanProperties, RecordBatchStream, SendableRecordBatchStream, Statistics,
+};
+
+use arrow::compute::filter_record_batch;
+use arrow::datatypes::{DataType, SchemaRef};
+use arrow::record_batch::RecordBatch;
+use arrow_array::{make_array, Array, ArrayRef, BooleanArray,
RecordBatchOptions};
+use arrow_data::transform::MutableArrayData;
+use arrow_schema::ArrowError;
+use datafusion_common::cast::as_boolean_array;
+use datafusion_common::stats::Precision;
+use datafusion_common::{internal_err, plan_err, DataFusionError, Result};
+use datafusion_execution::TaskContext;
+use datafusion_expr::Operator;
+use datafusion_physical_expr::expressions::BinaryExpr;
+use datafusion_physical_expr::intervals::utils::check_support;
+use datafusion_physical_expr::utils::collect_columns;
+use datafusion_physical_expr::{
+ analyze, split_conjunction, AnalysisContext, ConstExpr, ExprBoundaries,
PhysicalExpr,
+};
+
+use futures::stream::{Stream, StreamExt};
+use log::trace;
+
+/// This is a copy of DataFusion's FilterExec with one modification to ensure
that input
+/// batches are never passed through unchanged. The changes are between the
comments
+/// `BEGIN Comet change` and `END Comet change`.
+#[derive(Debug)]
+pub struct FilterExec {
+ /// The expression to filter on. This expression must evaluate to a
boolean value.
+ predicate: Arc<dyn PhysicalExpr>,
+ /// The input plan
+ input: Arc<dyn ExecutionPlan>,
+ /// Execution metrics
+ metrics: ExecutionPlanMetricsSet,
+ /// Selectivity for statistics. 0 = no rows, 100 = all rows
+ default_selectivity: u8,
+ /// Properties equivalence properties, partitioning, etc.
+ cache: PlanProperties,
+}
+
+impl FilterExec {
+ /// Create a FilterExec on an input
+ pub fn try_new(
+ predicate: Arc<dyn PhysicalExpr>,
+ input: Arc<dyn ExecutionPlan>,
+ ) -> Result<Self> {
+ match predicate.data_type(input.schema().as_ref())? {
+ DataType::Boolean => {
+ let default_selectivity = 20;
+ let cache = Self::compute_properties(&input, &predicate,
default_selectivity)?;
+ Ok(Self {
+ predicate,
+ input: Arc::clone(&input),
+ metrics: ExecutionPlanMetricsSet::new(),
+ default_selectivity,
+ cache,
+ })
+ }
+ other => {
+ plan_err!("Filter predicate must return BOOLEAN values, got
{other:?}")
+ }
+ }
+ }
+
+ pub fn with_default_selectivity(
+ mut self,
+ default_selectivity: u8,
+ ) -> Result<Self, DataFusionError> {
+ if default_selectivity > 100 {
+ return plan_err!(
+ "Default filter selectivity value needs to be less than or
equal to 100"
+ );
+ }
+ self.default_selectivity = default_selectivity;
+ Ok(self)
+ }
+
+ /// The expression to filter on. This expression must evaluate to a
boolean value.
+ pub fn predicate(&self) -> &Arc<dyn PhysicalExpr> {
+ &self.predicate
+ }
+
+ /// The input plan
+ pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
+ &self.input
+ }
+
+ /// The default selectivity
+ pub fn default_selectivity(&self) -> u8 {
+ self.default_selectivity
+ }
+
+ /// Calculates `Statistics` for `FilterExec`, by applying selectivity
(either default, or estimated) to input statistics.
+ fn statistics_helper(
+ input: &Arc<dyn ExecutionPlan>,
+ predicate: &Arc<dyn PhysicalExpr>,
+ default_selectivity: u8,
+ ) -> Result<Statistics> {
+ let input_stats = input.statistics()?;
+ let schema = input.schema();
+ if !check_support(predicate, &schema) {
+ let selectivity = default_selectivity as f64 / 100.0;
+ let mut stats = input_stats.into_inexact();
+ stats.num_rows =
stats.num_rows.with_estimated_selectivity(selectivity);
+ stats.total_byte_size = stats
+ .total_byte_size
+ .with_estimated_selectivity(selectivity);
+ return Ok(stats);
+ }
+
+ let num_rows = input_stats.num_rows;
+ let total_byte_size = input_stats.total_byte_size;
+ let input_analysis_ctx =
+ AnalysisContext::try_from_statistics(&input.schema(),
&input_stats.column_statistics)?;
+
+ let analysis_ctx = analyze(predicate, input_analysis_ctx, &schema)?;
+
+ // Estimate (inexact) selectivity of predicate
+ let selectivity = analysis_ctx.selectivity.unwrap_or(1.0);
+ let num_rows = num_rows.with_estimated_selectivity(selectivity);
+ let total_byte_size =
total_byte_size.with_estimated_selectivity(selectivity);
+
+ let column_statistics =
+ collect_new_statistics(&input_stats.column_statistics,
analysis_ctx.boundaries);
+ Ok(Statistics {
+ num_rows,
+ total_byte_size,
+ column_statistics,
+ })
+ }
+
+ fn extend_constants(
+ input: &Arc<dyn ExecutionPlan>,
+ predicate: &Arc<dyn PhysicalExpr>,
+ ) -> Vec<ConstExpr> {
+ let mut res_constants = Vec::new();
+ let input_eqs = input.equivalence_properties();
+
+ let conjunctions = split_conjunction(predicate);
+ for conjunction in conjunctions {
+ if let Some(binary) =
conjunction.as_any().downcast_ref::<BinaryExpr>() {
+ if binary.op() == &Operator::Eq {
+ // Filter evaluates to single value for all partitions
+ if input_eqs.is_expr_constant(binary.left()) {
+ res_constants
+
.push(ConstExpr::from(binary.right()).with_across_partitions(true))
+ } else if input_eqs.is_expr_constant(binary.right()) {
+ res_constants
+
.push(ConstExpr::from(binary.left()).with_across_partitions(true))
+ }
+ }
+ }
+ }
+ res_constants
+ }
+ /// This function creates the cache object that stores the plan properties
such as schema, equivalence properties, ordering, partitioning, etc.
+ fn compute_properties(
+ input: &Arc<dyn ExecutionPlan>,
+ predicate: &Arc<dyn PhysicalExpr>,
+ default_selectivity: u8,
+ ) -> Result<PlanProperties> {
+ // Combine the equal predicates with the input equivalence properties
+ // to construct the equivalence properties:
+ let stats = Self::statistics_helper(input, predicate,
default_selectivity)?;
+ let mut eq_properties = input.equivalence_properties().clone();
+ let (equal_pairs, _) = collect_columns_from_predicate(predicate);
+ for (lhs, rhs) in equal_pairs {
+ eq_properties.add_equal_conditions(lhs, rhs)?
+ }
+ // Add the columns that have only one viable value (singleton) after
+ // filtering to constants.
+ let constants = collect_columns(predicate)
+ .into_iter()
+ .filter(|column|
stats.column_statistics[column.index()].is_singleton())
+ .map(|column| {
+ let expr = Arc::new(column) as _;
+ ConstExpr::new(expr).with_across_partitions(true)
+ });
+ // this is for statistics
+ eq_properties = eq_properties.add_constants(constants);
+ // this is for logical constant (for example: a = '1', then a could be
marked as a constant)
+ // to do: how to deal with multiple situation to represent = (for
example c1 between 0 and 0)
+ eq_properties =
eq_properties.add_constants(Self::extend_constants(input, predicate));
+ Ok(PlanProperties::new(
+ eq_properties,
+ input.output_partitioning().clone(), // Output Partitioning
+ input.execution_mode(), // Execution Mode
+ ))
+ }
+}
+
+impl DisplayAs for FilterExec {
+ fn fmt_as(&self, t: DisplayFormatType, f: &mut std::fmt::Formatter) ->
std::fmt::Result {
+ match t {
+ DisplayFormatType::Default | DisplayFormatType::Verbose => {
+ write!(f, "FilterExec: {}", self.predicate)
+ }
+ }
+ }
+}
+
+impl ExecutionPlan for FilterExec {
+ fn name(&self) -> &'static str {
+ "FilterExec"
+ }
+
+ /// Return a reference to Any that can be used for downcasting
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn properties(&self) -> &PlanProperties {
+ &self.cache
+ }
+
+ fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
+ vec![&self.input]
+ }
+
+ fn maintains_input_order(&self) -> Vec<bool> {
+ // tell optimizer this operator doesn't reorder its input
+ vec![true]
+ }
+
+ fn with_new_children(
+ self: Arc<Self>,
+ mut children: Vec<Arc<dyn ExecutionPlan>>,
+ ) -> Result<Arc<dyn ExecutionPlan>> {
+ FilterExec::try_new(Arc::clone(&self.predicate),
children.swap_remove(0))
+ .and_then(|e| {
+ let selectivity = e.default_selectivity();
+ e.with_default_selectivity(selectivity)
+ })
+ .map(|e| Arc::new(e) as _)
+ }
+
+ fn execute(
+ &self,
+ partition: usize,
+ context: Arc<TaskContext>,
+ ) -> Result<SendableRecordBatchStream> {
+ trace!(
+ "Start FilterExec::execute for partition {} of context session_id
{} and task_id {:?}",
+ partition,
+ context.session_id(),
+ context.task_id()
+ );
+ let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
+ Ok(Box::pin(FilterExecStream {
+ schema: self.input.schema(),
+ predicate: Arc::clone(&self.predicate),
+ input: self.input.execute(partition, context)?,
+ baseline_metrics,
+ }))
+ }
+
+ fn metrics(&self) -> Option<MetricsSet> {
+ Some(self.metrics.clone_inner())
+ }
+
+ /// The output statistics of a filtering operation can be estimated if the
+ /// predicate's selectivity value can be determined for the incoming data.
+ fn statistics(&self) -> Result<Statistics> {
+ Self::statistics_helper(&self.input, self.predicate(),
self.default_selectivity)
+ }
+}
+
+/// This function ensures that all bounds in the `ExprBoundaries` vector are
+/// converted to closed bounds. If a lower/upper bound is initially open, it
+/// is adjusted by using the next/previous value for its data type to convert
+/// it into a closed bound.
+fn collect_new_statistics(
+ input_column_stats: &[ColumnStatistics],
+ analysis_boundaries: Vec<ExprBoundaries>,
+) -> Vec<ColumnStatistics> {
+ analysis_boundaries
+ .into_iter()
+ .enumerate()
+ .map(
+ |(
+ idx,
+ ExprBoundaries {
+ interval,
+ distinct_count,
+ ..
+ },
+ )| {
+ let (lower, upper) = interval.into_bounds();
+ let (min_value, max_value) = if lower.eq(&upper) {
+ (Precision::Exact(lower), Precision::Exact(upper))
+ } else {
+ (Precision::Inexact(lower), Precision::Inexact(upper))
+ };
+ ColumnStatistics {
+ null_count:
input_column_stats[idx].null_count.clone().to_inexact(),
+ max_value,
+ min_value,
+ distinct_count: distinct_count.to_inexact(),
+ }
+ },
+ )
+ .collect()
+}
+
+/// The FilterExec streams wraps the input iterator and applies the predicate
expression to
+/// determine which rows to include in its output batches
+struct FilterExecStream {
+ /// Output schema, which is the same as the input schema for this operator
+ schema: SchemaRef,
+ /// The expression to filter on. This expression must evaluate to a
boolean value.
+ predicate: Arc<dyn PhysicalExpr>,
+ /// The input partition to filter.
+ input: SendableRecordBatchStream,
+ /// runtime metrics recording
+ baseline_metrics: BaselineMetrics,
+}
+
+pub(crate) fn batch_filter(
+ batch: &RecordBatch,
+ predicate: &Arc<dyn PhysicalExpr>,
+) -> Result<RecordBatch> {
+ predicate
+ .evaluate(batch)
+ .and_then(|v| v.into_array(batch.num_rows()))
+ .and_then(|array| {
+ Ok(match as_boolean_array(&array) {
+ // apply filter array to record batch
+ Ok(filter_array) => comet_filter_record_batch(batch,
filter_array)?,
+ Err(_) => {
+ return internal_err!("Cannot create filter_array from
non-boolean predicates");
+ }
+ })
+ })
+}
+
+// BEGIN Comet changes
+// `FilterExec` could modify input batch or return input batch without change.
Instead of always
+// adding `CopyExec` on top of it, we only copy input batch for the special
case.
+pub fn comet_filter_record_batch(
+ record_batch: &RecordBatch,
+ predicate: &BooleanArray,
+) -> std::result::Result<RecordBatch, ArrowError> {
+ if predicate.true_count() == record_batch.num_rows() {
+ // special case where we just make an exact copy
+ let arrays: Vec<ArrayRef> = record_batch
+ .columns()
+ .iter()
+ .map(|array| {
+ let capacity = array.len();
+ let data = array.to_data();
+ let mut mutable = MutableArrayData::new(vec![&data], false,
capacity);
+ mutable.extend(0, 0, capacity);
+ make_array(mutable.freeze())
+ })
+ .collect();
+ let options =
RecordBatchOptions::new().with_row_count(Some(record_batch.num_rows()));
+ RecordBatch::try_new_with_options(record_batch.schema().clone(),
arrays, &options)
+ } else {
+ filter_record_batch(record_batch, predicate)
+ }
+}
+// END Comet changes
+
+impl Stream for FilterExecStream {
+ type Item = Result<RecordBatch>;
+
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) ->
Poll<Option<Self::Item>> {
+ let poll;
+ loop {
+ match ready!(self.input.poll_next_unpin(cx)) {
+ Some(Ok(batch)) => {
+ let timer =
self.baseline_metrics.elapsed_compute().timer();
+ let filtered_batch = batch_filter(&batch,
&self.predicate)?;
+ timer.done();
+ // skip entirely filtered batches
+ if filtered_batch.num_rows() == 0 {
+ continue;
+ }
+ poll = Poll::Ready(Some(Ok(filtered_batch)));
+ break;
+ }
+ value => {
+ poll = Poll::Ready(value);
+ break;
+ }
+ }
+ }
+ self.baseline_metrics.record_poll(poll)
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ // same number of record batches
+ self.input.size_hint()
+ }
+}
+
+impl RecordBatchStream for FilterExecStream {
+ fn schema(&self) -> SchemaRef {
+ Arc::clone(&self.schema)
+ }
+}
+
+/// Return the equals Column-Pairs and Non-equals Column-Pairs
+fn collect_columns_from_predicate(predicate: &Arc<dyn PhysicalExpr>) ->
EqualAndNonEqual {
+ let mut eq_predicate_columns = Vec::<PhysicalExprPairRef>::new();
+ let mut ne_predicate_columns = Vec::<PhysicalExprPairRef>::new();
+
+ let predicates = split_conjunction(predicate);
+ predicates.into_iter().for_each(|p| {
+ if let Some(binary) = p.as_any().downcast_ref::<BinaryExpr>() {
+ match binary.op() {
+ Operator::Eq => eq_predicate_columns.push((binary.left(),
binary.right())),
+ Operator::NotEq => ne_predicate_columns.push((binary.left(),
binary.right())),
+ _ => {}
+ }
+ }
+ });
+
+ (eq_predicate_columns, ne_predicate_columns)
+}
+
+/// Pair of `Arc<dyn PhysicalExpr>`s
+pub type PhysicalExprPairRef<'a> = (&'a Arc<dyn PhysicalExpr>, &'a Arc<dyn
PhysicalExpr>);
+
+/// The equals Column-Pairs and Non-equals Column-Pairs in the Predicates
+pub type EqualAndNonEqual<'a> = (Vec<PhysicalExprPairRef<'a>>,
Vec<PhysicalExprPairRef<'a>>);
diff --git a/native/core/src/execution/operators/mod.rs
b/native/core/src/execution/operators/mod.rs
index 09e05ef2..bdc233e9 100644
--- a/native/core/src/execution/operators/mod.rs
+++ b/native/core/src/execution/operators/mod.rs
@@ -22,9 +22,12 @@ use std::fmt::Debug;
use jni::objects::GlobalRef;
pub use copy::*;
+pub use filter::comet_filter_record_batch;
+pub use filter::FilterExec;
pub use scan::*;
mod copy;
+mod filter;
mod scan;
/// Error returned during executing operators.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]