use std::cmp::Ordering;
use std::sync::Arc;
use crate::EmptyRecordBatchStream;
use crate::joins::utils::{JoinFilter, JoinKeyComparator, compare_join_arrays};
use crate::metrics::{
BaselineMetrics, Count, ExecutionPlanMetricsSet, Gauge, MetricBuilder, Time,
};
use crate::spill::in_progress_spill_file::InProgressSpillFile;
use crate::spill::spill_manager::SpillManager;
use crate::stream::{ObservedStream, RecordBatchStreamAdapter};
use arrow::array::{Array, ArrayRef, BooleanArray, BooleanBufferBuilder, RecordBatch};
use arrow::compute::{BatchCoalescer, SortOptions, filter_record_batch, not};
use arrow::datatypes::SchemaRef;
use arrow::util::bit_chunk_iterator::UnalignedBitChunk;
use arrow::util::bit_util::apply_bitwise_binary_op;
use datafusion_common::instant::Instant;
use datafusion_common::{
DataFusionError, JoinSide, JoinType, NullEquality, Result, ScalarValue, internal_err,
};
use datafusion_execution::memory_pool::MemoryReservation;
use datafusion_execution::{
SendableRecordBatchStream, SpillFile, TryEmitter, async_try_stream,
};
use datafusion_physical_expr_common::physical_expr::PhysicalExprRef;
use futures::StreamExt;
fn evaluate_join_keys(
batch: &RecordBatch,
on: &[PhysicalExprRef],
) -> Result<Vec<ArrayRef>> {
on.iter()
.map(|expr| {
let num_rows = batch.num_rows();
let val = expr.evaluate(batch)?;
val.into_array(num_rows)
})
.collect()
}
fn find_key_group_end(cmp: &JoinKeyComparator, from: usize, len: usize) -> usize {
let next = from + 1;
if next >= len {
return len;
}
if cmp.compare(from, next) != Ordering::Equal {
return next;
}
let last = len - 1;
if cmp.compare(from, last) == Ordering::Equal {
return len;
}
let mut lo = next + 1;
let mut hi = last;
while lo < hi {
let mid = lo + (hi - lo) / 2;
if cmp.compare(from, mid) == Ordering::Equal {
lo = mid + 1;
} else {
hi = mid;
}
}
lo
}
pub(crate) struct BitwiseSortMergeJoinStream {
join_type: JoinType,
outer: SendableRecordBatchStream,
inner: SendableRecordBatchStream,
outer_batch: Option<RecordBatch>,
outer_offset: usize,
outer_key_arrays: Vec<ArrayRef>,
inner_batch: Option<RecordBatch>,
inner_offset: usize,
inner_key_arrays: Vec<ArrayRef>,
matched: BooleanBufferBuilder,
inner_key_buffer: Vec<RecordBatch>,
on_outer: Vec<PhysicalExprRef>,
on_inner: Vec<PhysicalExprRef>,
filter: Option<JoinFilter>,
sort_options: Vec<SortOptions>,
null_equality: NullEquality,
outer_is_left: bool,
coalescer: BatchCoalescer,
schema: SchemaRef,
input_batches: Count,
input_rows: Count,
peak_mem_used: Gauge,
join_time: Time,
join_time_start: Option<Instant>,
reservation: MemoryReservation,
spill_manager: SpillManager,
runtime_env: Arc<datafusion_execution::runtime_env::RuntimeEnv>,
inner_buffer_size: usize,
outer_inner_cmp: Option<JoinKeyComparator>,
outer_self_cmp: Option<JoinKeyComparator>,
inner_self_cmp: Option<JoinKeyComparator>,
}
impl BitwiseSortMergeJoinStream {
#[expect(clippy::too_many_arguments)]
pub fn try_new(
schema: SchemaRef,
sort_options: Vec<SortOptions>,
null_equality: NullEquality,
outer: SendableRecordBatchStream,
inner: SendableRecordBatchStream,
on_outer: Vec<PhysicalExprRef>,
on_inner: Vec<PhysicalExprRef>,
filter: Option<JoinFilter>,
join_type: JoinType,
batch_size: usize,
partition: usize,
metrics: &ExecutionPlanMetricsSet,
reservation: MemoryReservation,
spill_manager: SpillManager,
runtime_env: Arc<datafusion_execution::runtime_env::RuntimeEnv>,
) -> Result<SendableRecordBatchStream> {
debug_assert!(
matches!(
join_type,
JoinType::LeftSemi
| JoinType::RightSemi
| JoinType::LeftAnti
| JoinType::RightAnti
| JoinType::LeftMark
| JoinType::RightMark
),
"BitwiseSortMergeJoinStream does not handle {join_type:?}"
);
let outer_is_left = matches!(
join_type,
JoinType::LeftSemi | JoinType::LeftAnti | JoinType::LeftMark
);
let join_time = MetricBuilder::new(metrics).subset_time("join_time", partition);
let input_batches =
MetricBuilder::new(metrics).counter("input_batches", partition);
let input_rows = MetricBuilder::new(metrics).counter("input_rows", partition);
let baseline_metrics = BaselineMetrics::new(metrics, partition);
let peak_mem_used =
MetricBuilder::new(metrics).peak_memory_usage("peak_mem_used", partition);
let mut state = Self {
join_type,
outer,
inner,
outer_batch: None,
outer_offset: 0,
outer_key_arrays: vec![],
inner_batch: None,
inner_offset: 0,
inner_key_arrays: vec![],
matched: BooleanBufferBuilder::new(0),
inner_key_buffer: vec![],
on_outer,
on_inner,
filter,
sort_options,
null_equality,
outer_is_left,
coalescer: BatchCoalescer::new(Arc::clone(&schema), batch_size)
.with_biggest_coalesce_batch_size(Some(batch_size / 2)),
schema: Arc::clone(&schema),
input_batches,
input_rows,
peak_mem_used,
join_time,
join_time_start: None,
reservation,
spill_manager,
runtime_env,
inner_buffer_size: 0,
outer_inner_cmp: None,
outer_self_cmp: None,
inner_self_cmp: None,
};
let stream = async_try_stream(|mut emitter| async move {
state.start_join_time();
let result = state.join(&mut emitter).await;
state.stop_join_time();
result
});
Ok(Box::pin(ObservedStream::new(
Box::pin(RecordBatchStreamAdapter::new(schema, stream)),
baseline_metrics,
None,
)))
}
fn start_join_time(&mut self) {
debug_assert!(self.join_time_start.is_none(), "join_time already running");
self.join_time_start = Some(Instant::now());
}
fn stop_join_time(&mut self) {
if let Some(start) = self.join_time_start.take() {
self.join_time.add_elapsed(start);
}
}
fn try_resize_reservation(&mut self) -> Result<()> {
let needed = self.inner_buffer_size;
self.reservation.try_resize(needed)?;
self.peak_mem_used.set_max(self.reservation.size());
Ok(())
}
fn get_outer_inner_cmp(&mut self) -> Result<&JoinKeyComparator> {
if self.outer_inner_cmp.is_none() {
self.outer_inner_cmp = Some(JoinKeyComparator::new(
&self.outer_key_arrays,
&self.inner_key_arrays,
&self.sort_options,
self.null_equality,
)?);
}
Ok(self.outer_inner_cmp.as_ref().unwrap())
}
fn get_outer_self_cmp(&mut self) -> Result<&JoinKeyComparator> {
if self.outer_self_cmp.is_none() {
self.outer_self_cmp = Some(JoinKeyComparator::new(
&self.outer_key_arrays,
&self.outer_key_arrays,
&self.sort_options,
self.null_equality,
)?);
}
Ok(self.outer_self_cmp.as_ref().unwrap())
}
fn get_inner_self_cmp(&mut self) -> Result<&JoinKeyComparator> {
if self.inner_self_cmp.is_none() {
self.inner_self_cmp = Some(JoinKeyComparator::new(
&self.inner_key_arrays,
&self.inner_key_arrays,
&self.sort_options,
self.null_equality,
)?);
}
Ok(self.inner_self_cmp.as_ref().unwrap())
}
fn spill_inner_key_buffer(
&mut self,
writer: &mut Option<InProgressSpillFile>,
) -> Result<()> {
if writer.is_none() {
*writer = Some(
self.spill_manager
.create_in_progress_file("semi_anti_smj_inner_key_spill")?,
);
}
let writer = writer.as_mut().unwrap();
for batch in self.inner_key_buffer.drain(..) {
writer.append_batch(&batch)?;
}
self.inner_buffer_size = 0;
self.try_resize_reservation()
}
fn clear_inner_key_group(&mut self) {
self.inner_key_buffer.clear();
self.inner_buffer_size = 0;
}
async fn next_outer_batch(&mut self) -> Result<bool> {
loop {
self.stop_join_time();
let item = self.outer.next().await;
self.start_join_time();
match item {
None => {
let outer_schema = self.outer.schema();
self.outer = Box::pin(EmptyRecordBatchStream::new(outer_schema));
return Ok(false);
}
Some(Err(e)) => return Err(e),
Some(Ok(batch)) => {
let batch_num_rows = batch.num_rows();
self.input_batches.add(1);
self.input_rows.add(batch_num_rows);
if batch_num_rows == 0 {
continue;
}
let keys = evaluate_join_keys(&batch, &self.on_outer)?;
self.outer_batch = Some(batch);
self.outer_offset = 0;
self.outer_key_arrays = keys;
self.outer_inner_cmp = None;
self.outer_self_cmp = None;
self.matched = BooleanBufferBuilder::new(batch_num_rows);
self.matched.append_n(batch_num_rows, false);
return Ok(true);
}
}
}
}
async fn next_inner_batch(&mut self) -> Result<bool> {
loop {
self.stop_join_time();
let item = self.inner.next().await;
self.start_join_time();
match item {
None => {
let inner_schema = self.inner.schema();
self.inner = Box::pin(EmptyRecordBatchStream::new(inner_schema));
return Ok(false);
}
Some(Err(e)) => return Err(e),
Some(Ok(batch)) => {
let batch_num_rows = batch.num_rows();
self.input_batches.add(1);
self.input_rows.add(batch_num_rows);
if batch_num_rows == 0 {
continue;
}
let keys = evaluate_join_keys(&batch, &self.on_inner)?;
self.inner_batch = Some(batch);
self.inner_offset = 0;
self.inner_key_arrays = keys;
self.outer_inner_cmp = None;
self.inner_self_cmp = None;
return Ok(true);
}
}
}
}
fn emit_outer_batch(&mut self) -> Result<()> {
let batch = self.outer_batch.take().unwrap();
let matched_buf = self.matched.finish();
match self.join_type {
JoinType::LeftMark | JoinType::RightMark => {
debug_assert_eq!(
self.schema.fields().len(),
batch.num_columns() + 1,
"Mark join output schema should be outer schema + 1 mark column"
);
let mark_col = Arc::new(BooleanArray::new(matched_buf, None)) as ArrayRef;
let mut columns = Vec::with_capacity(batch.num_columns() + 1);
columns.extend_from_slice(batch.columns());
columns.push(mark_col);
let output = RecordBatch::try_new(Arc::clone(&self.schema), columns)?;
self.coalescer.push_batch(output)?;
}
JoinType::LeftSemi | JoinType::RightSemi => {
let selection = BooleanArray::new(matched_buf, None);
let filtered = filter_record_batch(&batch, &selection)?;
if filtered.num_rows() > 0 {
self.coalescer.push_batch(filtered)?;
}
}
JoinType::LeftAnti | JoinType::RightAnti => {
let selection = not(&BooleanArray::new(matched_buf, None))?;
let filtered = filter_record_batch(&batch, &selection)?;
if filtered.num_rows() > 0 {
self.coalescer.push_batch(filtered)?;
}
}
_ => unreachable!(),
}
Ok(())
}
fn mark_outer_key_group_matched(&mut self) -> Result<()> {
let num_outer = self.outer_batch.as_ref().unwrap().num_rows();
let from = self.outer_offset;
let group_end = find_key_group_end(self.get_outer_self_cmp()?, from, num_outer);
for i in from..group_end {
self.matched.set_bit(i, true);
}
self.outer_offset = group_end;
Ok(())
}
async fn advance_inner_past_key_group(&mut self) -> Result<()> {
loop {
let Some(inner_batch) = &self.inner_batch else {
return Ok(());
};
let num_inner = inner_batch.num_rows();
let from = self.inner_offset;
let group_end =
find_key_group_end(self.get_inner_self_cmp()?, from, num_inner);
if group_end < num_inner {
self.inner_offset = group_end;
return Ok(());
}
let saved_inner_keys = slice_keys(&self.inner_key_arrays, num_inner - 1);
if !self.next_inner_batch().await? {
self.inner_batch = None;
return Ok(());
}
if !keys_match(
&saved_inner_keys,
&self.inner_key_arrays,
&self.sort_options,
self.null_equality,
)? {
return Ok(());
}
}
}
async fn buffer_inner_key_group(&mut self) -> Result<Option<Arc<dyn SpillFile>>> {
self.clear_inner_key_group();
let mut writer: Option<InProgressSpillFile> = None;
while let Some(inner_batch) = &self.inner_batch {
let num_inner = inner_batch.num_rows();
let from = self.inner_offset;
let group_end =
find_key_group_end(self.get_inner_self_cmp()?, from, num_inner);
let inner_batch = self.inner_batch.as_ref().unwrap();
let slice = inner_batch.slice(from, group_end - from);
self.inner_buffer_size += slice.get_array_memory_size();
self.inner_key_buffer.push(slice);
if self.try_resize_reservation().is_err() {
if self.runtime_env.disk_manager.tmp_files_enabled() {
self.spill_inner_key_buffer(&mut writer)?;
} else {
self.try_resize_reservation().map_err(|e| {
DataFusionError::Execution(format!(
"{e}. Disk spilling disabled."
))
})?;
}
}
if group_end < num_inner {
self.inner_offset = group_end;
break;
}
let saved_inner_keys = slice_keys(&self.inner_key_arrays, num_inner - 1);
if !self.next_inner_batch().await? {
self.inner_batch = None;
break;
}
if !keys_match(
&saved_inner_keys,
&self.inner_key_arrays,
&self.sort_options,
self.null_equality,
)? {
break;
}
}
match writer {
Some(mut writer) => writer.finish(),
None => Ok(None),
}
}
async fn process_key_match_with_filter(
&mut self,
spill: Option<&Arc<dyn SpillFile>>,
) -> Result<()> {
let num_outer = self.outer_batch.as_ref().unwrap().num_rows();
debug_assert!(
!self.inner_key_buffer.is_empty() || spill.is_some(),
"process_key_match_with_filter called with no inner key data"
);
debug_assert!(
self.outer_offset < num_outer,
"outer_offset must be within the current batch"
);
debug_assert!(
self.matched.len() == num_outer,
"matched vector must be sized for the current outer batch"
);
let outer_group_start = self.outer_offset;
let outer_group_end =
find_key_group_end(self.get_outer_self_cmp()?, outer_group_start, num_outer);
let outer_group_len = outer_group_end - outer_group_start;
let filter = self.filter.as_ref().unwrap();
let outer_batch = self.outer_batch.as_ref().unwrap();
let outer_slice = outer_batch.slice(outer_group_start, outer_group_len);
let mut matched_count = UnalignedBitChunk::new(
self.matched.as_slice(),
outer_group_start,
outer_group_len,
)
.count_ones();
if matched_count < outer_group_len
&& let Some(spill_file) = spill
{
let mut spill_stream = self
.spill_manager
.read_spill_as_stream(Arc::clone(spill_file), None)?;
let mut spill_stream_has_data = false;
while matched_count < outer_group_len {
match spill_stream.next().await {
Some(Ok(inner_slice)) => {
spill_stream_has_data = true;
matched_count = eval_filter_for_inner_slice(
self.outer_is_left,
filter,
&outer_slice,
&inner_slice,
&mut self.matched,
outer_group_start,
outer_group_len,
matched_count,
)?;
}
Some(Err(e)) => return Err(e),
None => {
if !spill_stream_has_data {
return internal_err!("Spill file was empty");
}
break;
}
}
}
}
if matched_count < outer_group_len {
'outer: for inner_slice in &self.inner_key_buffer {
matched_count = eval_filter_for_inner_slice(
self.outer_is_left,
filter,
&outer_slice,
inner_slice,
&mut self.matched,
outer_group_start,
outer_group_len,
matched_count,
)?;
if matched_count == outer_group_len {
break 'outer;
}
}
}
self.outer_offset = outer_group_end;
Ok(())
}
async fn process_filtered_match_loop(
&mut self,
spill: Option<Arc<dyn SpillFile>>,
) -> Result<()> {
loop {
self.process_key_match_with_filter(spill.as_ref()).await?;
let outer_batch = self.outer_batch.as_ref().unwrap();
if self.outer_offset < outer_batch.num_rows() {
break;
}
let saved_keys =
slice_keys(&self.outer_key_arrays, outer_batch.num_rows() - 1);
self.emit_outer_batch()?;
if !self.next_outer_batch().await? {
break;
}
if !keys_match(
&saved_keys,
&self.outer_key_arrays,
&self.sort_options,
self.null_equality,
)? {
break;
}
}
self.clear_inner_key_group();
Ok(())
}
async fn process_unfiltered_match_loop(&mut self) -> Result<()> {
loop {
self.mark_outer_key_group_matched()?;
let outer_batch = self.outer_batch.as_ref().unwrap();
if self.outer_offset < outer_batch.num_rows() {
return Ok(());
}
let saved_keys =
slice_keys(&self.outer_key_arrays, outer_batch.num_rows() - 1);
self.emit_outer_batch()?;
if !self.next_outer_batch().await? {
return Ok(());
}
if !keys_match(
&saved_keys,
&self.outer_key_arrays,
&self.sort_options,
self.null_equality,
)? {
return Ok(());
}
}
}
async fn process_key_match(&mut self) -> Result<()> {
if self.filter.is_some() {
let spill = self.buffer_inner_key_group().await?;
self.process_filtered_match_loop(spill).await
} else {
self.advance_inner_past_key_group().await?;
self.process_unfiltered_match_loop().await
}
}
fn compare_current_keys(&mut self) -> Result<Ordering> {
let (outer_idx, inner_idx) = (self.outer_offset, self.inner_offset);
Ok(self.get_outer_inner_cmp()?.compare(outer_idx, inner_idx))
}
fn skip_outer_key_group(&mut self) -> Result<()> {
let num_outer = self.outer_batch.as_ref().unwrap().num_rows();
let from = self.outer_offset;
self.outer_offset =
find_key_group_end(self.get_outer_self_cmp()?, from, num_outer);
Ok(())
}
fn try_skip_inner_key_group(&mut self) -> Result<bool> {
let num_inner = self.inner_batch.as_ref().unwrap().num_rows();
let from = self.inner_offset;
let group_end = find_key_group_end(self.get_inner_self_cmp()?, from, num_inner);
if group_end >= num_inner {
return Ok(false);
}
self.inner_offset = group_end;
Ok(true)
}
fn try_process_key_match(&mut self) -> Result<bool> {
if self.filter.is_some() {
return Ok(false);
}
let num_inner = self.inner_batch.as_ref().unwrap().num_rows();
let inner_from = self.inner_offset;
let inner_group_end =
find_key_group_end(self.get_inner_self_cmp()?, inner_from, num_inner);
if inner_group_end >= num_inner {
return Ok(false);
}
let num_outer = self.outer_batch.as_ref().unwrap().num_rows();
let outer_from = self.outer_offset;
let outer_group_end =
find_key_group_end(self.get_outer_self_cmp()?, outer_from, num_outer);
if outer_group_end >= num_outer {
return Ok(false);
}
for i in outer_from..outer_group_end {
self.matched.set_bit(i, true);
}
self.outer_offset = outer_group_end;
self.inner_offset = inner_group_end;
Ok(true)
}
fn has_current_outer_row(&self) -> bool {
self.outer_batch
.as_ref()
.is_some_and(|batch| self.outer_offset < batch.num_rows())
}
fn has_current_inner_row(&self) -> bool {
self.inner_batch
.as_ref()
.is_some_and(|batch| self.inner_offset < batch.num_rows())
}
async fn advance_outer_row(
&mut self,
emitter: &mut TryEmitter<RecordBatch, DataFusionError>,
) -> Result<bool> {
loop {
match &self.outer_batch {
Some(batch) if self.outer_offset < batch.num_rows() => {
return Ok(true);
}
Some(_) => {
self.emit_outer_batch()?;
self.emit_completed_batches(emitter).await;
}
None => {
if !self.next_outer_batch().await? {
return Ok(false);
}
}
}
}
}
async fn advance_inner_row(&mut self) -> Result<bool> {
loop {
if let Some(batch) = &self.inner_batch
&& self.inner_offset < batch.num_rows()
{
return Ok(true);
}
if !self.next_inner_batch().await? {
self.inner_batch = None;
return Ok(false);
}
}
}
async fn drain_outer(&mut self) -> Result<()> {
self.emit_outer_batch()?;
while self.next_outer_batch().await? {
self.emit_outer_batch()?;
}
Ok(())
}
async fn emit_completed_batches(
&mut self,
emitter: &mut TryEmitter<RecordBatch, DataFusionError>,
) {
while let Some(batch) = self.coalescer.next_completed_batch() {
self.stop_join_time();
emitter.emit(batch).await;
self.start_join_time();
}
}
async fn join(
&mut self,
emitter: &mut TryEmitter<RecordBatch, DataFusionError>,
) -> Result<()> {
while self.has_current_outer_row() || self.advance_outer_row(emitter).await? {
if !(self.has_current_inner_row() || self.advance_inner_row().await?) {
self.drain_outer().await?;
break;
}
match self.compare_current_keys()? {
Ordering::Less => self.skip_outer_key_group()?,
Ordering::Greater => {
if !self.try_skip_inner_key_group()? {
self.advance_inner_past_key_group().await?;
}
}
Ordering::Equal => {
if !self.try_process_key_match()? {
self.process_key_match().await?;
}
}
}
if self.coalescer.has_completed_batch() {
self.emit_completed_batches(emitter).await;
}
}
self.coalescer.finish_buffered_batch()?;
self.emit_completed_batches(emitter).await;
Ok(())
}
}
#[expect(clippy::too_many_arguments)]
fn eval_filter_for_inner_slice(
outer_is_left: bool,
filter: &JoinFilter,
outer_slice: &RecordBatch,
inner_slice: &RecordBatch,
matched: &mut BooleanBufferBuilder,
outer_offset: usize,
outer_group_len: usize,
mut matched_count: usize,
) -> Result<usize> {
debug_assert_eq!(
matched_count,
UnalignedBitChunk::new(matched.as_slice(), outer_offset, outer_group_len)
.count_ones()
);
for inner_row in 0..inner_slice.num_rows() {
if matched_count == outer_group_len {
break;
}
let filter_result = evaluate_filter_for_inner_row(
outer_is_left,
filter,
outer_slice,
inner_slice,
inner_row,
)?;
let filter_buf = filter_result.values();
apply_bitwise_binary_op(
matched.as_slice_mut(),
outer_offset,
filter_buf.inner().as_slice(),
filter_buf.offset(),
outer_group_len,
|a, b| a | b,
);
matched_count =
UnalignedBitChunk::new(matched.as_slice(), outer_offset, outer_group_len)
.count_ones();
}
Ok(matched_count)
}
fn slice_keys(keys: &[ArrayRef], idx: usize) -> Vec<ArrayRef> {
keys.iter().map(|a| a.slice(idx, 1)).collect()
}
fn keys_match(
left_arrays: &[ArrayRef],
right_arrays: &[ArrayRef],
sort_options: &[SortOptions],
null_equality: NullEquality,
) -> Result<bool> {
debug_assert!(left_arrays.iter().all(|a| a.len() == 1));
let cmp = compare_join_arrays(
left_arrays,
0,
right_arrays,
0,
sort_options,
null_equality,
)?;
Ok(cmp == Ordering::Equal)
}
fn evaluate_filter_for_inner_row(
outer_is_left: bool,
filter: &JoinFilter,
outer_slice: &RecordBatch,
inner_batch: &RecordBatch,
inner_idx: usize,
) -> Result<BooleanArray> {
let num_outer_rows = outer_slice.num_rows();
let mut columns: Vec<ArrayRef> = Vec::with_capacity(filter.column_indices().len());
for col_idx in filter.column_indices() {
let (side_batch, side_idx) = if outer_is_left {
match col_idx.side {
JoinSide::Left => (outer_slice, None),
JoinSide::Right => (inner_batch, Some(inner_idx)),
JoinSide::None => {
return internal_err!("Unexpected JoinSide::None in filter");
}
}
} else {
match col_idx.side {
JoinSide::Left => (inner_batch, Some(inner_idx)),
JoinSide::Right => (outer_slice, None),
JoinSide::None => {
return internal_err!("Unexpected JoinSide::None in filter");
}
}
};
match side_idx {
None => {
columns.push(Arc::clone(side_batch.column(col_idx.index)));
}
Some(idx) => {
let scalar = ScalarValue::try_from_array(
side_batch.column(col_idx.index).as_ref(),
idx,
)?;
columns.push(scalar.to_array_of_size(num_outer_rows)?);
}
}
}
let filter_batch = RecordBatch::try_new(Arc::clone(filter.schema()), columns)?;
let result = filter
.expression()
.evaluate(&filter_batch)?
.into_array(num_outer_rows)?;
let bool_arr = result
.as_any()
.downcast_ref::<BooleanArray>()
.ok_or_else(|| {
DataFusionError::Internal(
"Filter expression did not return BooleanArray".to_string(),
)
})?;
if bool_arr.null_count() > 0 {
Ok(arrow::compute::prep_null_mask_filter(bool_arr))
} else {
Ok(bool_arr.clone())
}
}