Skip to main content

datafusion_physical_plan/joins/piecewise_merge_join/
exec.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
18use arrow::array::Array;
19use arrow::{
20    array::{ArrayRef, BooleanBufferBuilder, RecordBatch},
21    compute::concat_batches,
22    util::bit_util,
23};
24use arrow_schema::{SchemaRef, SortOptions};
25use datafusion_common::not_impl_err;
26use datafusion_common::tree_node::TreeNodeRecursion;
27use datafusion_common::{JoinSide, Result, internal_err};
28use datafusion_execution::{
29    SendableRecordBatchStream,
30    memory_pool::{MemoryConsumer, MemoryReservation},
31};
32use datafusion_expr::{JoinType, Operator};
33use datafusion_physical_expr::equivalence::join_equivalence_properties;
34use datafusion_physical_expr::{
35    Distribution, LexOrdering, OrderingRequirements, PhysicalExpr, PhysicalExprRef,
36    PhysicalSortExpr,
37};
38use datafusion_physical_expr_common::physical_expr::fmt_sql;
39use futures::TryStreamExt;
40use parking_lot::Mutex;
41use std::fmt::Formatter;
42use std::sync::Arc;
43use std::sync::atomic::AtomicUsize;
44
45use crate::execution_plan::{EmissionType, boundedness_from_children};
46
47use crate::joins::piecewise_merge_join::classic_join::{
48    ClassicPWMJStream, PiecewiseMergeJoinStreamState,
49};
50use crate::joins::piecewise_merge_join::utils::{
51    build_visited_indices_map, is_existence_join, is_right_existence_join,
52};
53use crate::joins::utils::asymmetric_join_output_partitioning;
54use crate::metrics::MetricsSet;
55use crate::{
56    ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlanProperties,
57    ReplaceChildrenOptions, validate_child_count,
58};
59use crate::{
60    ExecutionPlan, PlanProperties,
61    joins::{
62        SharedBitmapBuilder,
63        utils::{BuildProbeJoinMetrics, OnceAsync, OnceFut, build_join_schema},
64    },
65    metrics::ExecutionPlanMetricsSet,
66    spill::get_record_batch_memory_size,
67};
68
69/// `PiecewiseMergeJoinExec` is a join execution plan that only evaluates single range filter and show much
70/// better performance for these workloads than `NestedLoopJoin`
71///
72/// The physical planner will choose to evaluate this join when there is only one comparison filter. This
73/// is a binary expression which contains [`Operator::Lt`], [`Operator::LtEq`], [`Operator::Gt`], and
74/// [`Operator::GtEq`].:
75/// Examples:
76///  - `col0` < `colb`, `col0` <= `colb`, `col0` > `colb`, `col0` >= `colb`
77///
78/// # Execution Plan Inputs
79/// For `PiecewiseMergeJoin` we label all right inputs as the `streamed' side and the left outputs as the
80/// 'buffered' side.
81///
82/// `PiecewiseMergeJoin` takes a sorted input for the side to be buffered and is able to sort streamed record
83/// batches during processing. Sorted input must specifically be ascending/descending based on the operator.
84///
85/// # Algorithms
86/// Classic joins are processed differently compared to existence joins.
87///
88/// ## Classic Joins (Inner, Full, Left, Right)
89/// For classic joins we buffer the build side and stream the probe side (the "probe" side).
90/// Both sides are sorted so that we can iterate from index 0 to the end on each side.  This ordering ensures
91/// that when we find the first matching pair of rows, we can emit the current stream row joined with all remaining
92/// probe rows from the match position onward, without rescanning earlier probe rows.
93///
94/// For `<` and `<=` operators, both inputs are sorted in **descending** order, while for `>` and `>=` operators
95/// they are sorted in **ascending** order. This choice ensures that the pointer on the buffered side can advance
96/// monotonically as we stream new batches from the stream side.
97///
98/// The streamed side may arrive unsorted, so this operator sorts each incoming batch in memory before
99/// processing. The buffered side is required to be globally sorted; the plan declares this requirement
100/// in `requires_input_order`, which allows the optimizer to automatically insert a `SortExec` on that side if needed.
101/// By the time this operator runs, the buffered side is guaranteed to be in the proper order.
102///
103/// The pseudocode for the algorithm looks like this:
104///
105/// ```text
106/// for stream_row in stream_batch:
107///     for buffer_row in buffer_batch:
108///         if compare(stream_row, probe_row):
109///             output stream_row X buffer_batch[buffer_row:]
110///         else:
111///             continue
112/// ```
113///
114/// The algorithm uses the streamed side (larger) to drive the loop. This is due to every row on the stream side iterating
115/// the buffered side to find every first match. By doing this, each match can output more result so that output
116/// handling can be better vectorized for performance.
117///
118/// Here is an example:
119///
120/// We perform a `JoinType::Left` with these two batches and the operator being `Operator::Lt`(<). For each
121/// row on the streamed side we move a pointer on the buffered until it matches the condition. Once we reach
122/// the row which matches (in this case with row 1 on streamed will have its first match on row 2 on
123/// buffered; 100 < 200 is true), we can emit all rows after that match. We can emit the rows like this because
124/// if the batch is sorted in ascending order, every subsequent row will also satisfy the condition as they will
125/// all be larger values.
126///
127/// ```text
128/// SQL statement:
129/// SELECT *
130/// FROM (VALUES (100), (200), (500)) AS streamed(a)
131/// LEFT JOIN (VALUES (100), (200), (200), (300), (400)) AS buffered(b)
132///   ON streamed.a < buffered.b;
133///
134/// Processing Row 1:
135///
136///       Sorted Buffered Side                                         Sorted Streamed Side
137///       ┌──────────────────┐                                         ┌──────────────────┐
138///     1 │       100        │                                       1 │       100        │
139///       ├──────────────────┤                                         ├──────────────────┤
140///     2 │       200        │ ─┐                                    2 │       200        │
141///       ├──────────────────┤  │  For row 1 on streamed side with     ├──────────────────┤
142///     3 │       200        │  │  value 100, we emit rows 2 - 5.    3 │       500        │
143///       ├──────────────────┤  │  as matches when the operator is     └──────────────────┘
144///     4 │       300        │  │  `Operator::Lt` (<) Emitting all
145///       ├──────────────────┤  │  rows after the first match (row
146///     5 │       400        │ ─┘  2 buffered side; 100 < 200)
147///       └──────────────────┘
148///
149/// Processing Row 2:
150///   By sorting the streamed side we know
151///
152///       Sorted Buffered Side                                         Sorted Streamed Side
153///       ┌──────────────────┐                                         ┌──────────────────┐
154///     1 │       100        │                                       1 │       100        │
155///       ├──────────────────┤                                         ├──────────────────┤
156///     2 │       200        │ <- Start here when probing for the    2 │       200        │
157///       ├──────────────────┤    streamed side row 2.                 ├──────────────────┤
158///     3 │       200        │                                       3 │       500        │
159///       ├──────────────────┤                                         └──────────────────┘
160///     4 │       300        │
161///       ├──────────────────┤
162///     5 │       400        │
163///       └──────────────────┘
164/// ```
165///
166/// ## Existence Joins (Semi, Anti, Mark)
167/// Existence joins are made magnitudes of times faster with a `PiecewiseMergeJoin` as we only need to find
168/// the min/max value of the streamed side to be able to emit all matches on the buffered side. By putting
169/// the side we need to mark onto the sorted buffer side, we can emit all these matches at once.
170///
171/// For less than operations (`<`) both inputs are to be sorted in descending order and vice versa for greater
172/// than (`>`) operations. `SortExec` is used to enforce sorting on the buffered side and streamed side does not
173/// need to be sorted due to only needing to find the min/max.
174///
175/// For Left Semi, Anti, and Mark joins we swap the inputs so that the marked side is on the buffered side.
176///
177/// The pseudocode for the algorithm looks like this:
178///
179/// ```text
180/// // Using the example of a less than `<` operation
181/// let max = max_batch(streamed_batch)
182///
183/// for buffer_row in buffer_batch:
184///     if buffer_row < max:
185///         output buffer_batch[buffer_row:]
186/// ```
187///
188/// Only need to find the min/max value and iterate through the buffered side once.
189///
190/// Here is an example:
191/// We perform a `JoinType::LeftSemi` with these two batches and the operator being `Operator::Lt`(<). Because
192/// the operator is `Operator::Lt` we can find the minimum value in the streamed side; in this case it is 200.
193/// We can then advance a pointer from the start of the buffer side until we find the first value that satisfies
194/// the predicate. All rows after that first matched value satisfy the condition 200 < x so we can mark all of
195/// those rows as matched.
196///
197/// ```text
198/// SQL statement:
199/// SELECT *
200/// FROM (VALUES (500), (200), (300)) AS streamed(a)
201/// LEFT SEMI JOIN (VALUES (100), (200), (200), (300), (400)) AS buffered(b)
202///   ON streamed.a < buffered.b;
203///
204///          Sorted Buffered Side             Unsorted Streamed Side
205///            ┌──────────────────┐          ┌──────────────────┐
206///          1 │       100        │        1 │       500        │
207///            ├──────────────────┤          ├──────────────────┤
208///          2 │       200        │        2 │       200        │
209///            ├──────────────────┤          ├──────────────────┤
210///          3 │       200        │        3 │       300        │
211///            ├──────────────────┤          └──────────────────┘
212///          4 │       300        │ ─┐
213///            ├──────────────────┤  | We emit matches for row 4 - 5
214///          5 │       400        │ ─┘ on the buffered side.
215///            └──────────────────┘
216///             min value: 200
217/// ```
218///
219/// For both types of joins, the buffered side must be sorted ascending for `Operator::Lt` (<) or
220/// `Operator::LtEq` (<=) and descending for `Operator::Gt` (>) or `Operator::GtEq` (>=).
221///
222/// # Partitioning Logic
223/// Piecewise Merge Join requires one buffered side partition + round robin partitioned stream side. A counter
224/// is used in the buffered side to coordinate when all streamed partitions are finished execution. This allows
225/// for processing the rest of the unmatched rows for Left and Full joins. The last partition that finishes
226/// execution will be responsible for outputting the unmatched rows.
227///
228/// # Performance Explanation (cost)
229/// Piecewise Merge Join is used over Nested Loop Join due to its superior performance. Here is the breakdown:
230///
231/// R: Buffered Side
232/// S: Streamed Side
233///
234/// ## Piecewise Merge Join (PWMJ)
235///
236/// # Classic Join:
237/// Requires sorting the probe side and, for each probe row, scanning the buffered side until the first match
238/// is found.
239///     Complexity: `O(sort(S) + num_of_batches(|S|) * scan(R))`.
240///
241/// # Mark Join:
242/// Sorts the probe side, then computes the min/max range of the probe keys and scans the buffered side only
243/// within that range.
244///   Complexity: `O(|S| + scan(R[range]))`.
245///
246/// ## Nested Loop Join
247/// Compares every row from `S` with every row from `R`.
248///   Complexity: `O(|S| * |R|)`.
249///
250/// ## Nested Loop Join
251///   Always going to be probe (O(S) * O(R)).
252///
253/// # Further Reference Material
254/// DuckDB blog on Range Joins: [Range Joins in DuckDB](https://duckdb.org/2022/05/27/iejoin.html)
255#[derive(Debug)]
256pub struct PiecewiseMergeJoinExec {
257    /// Left buffered execution plan
258    pub buffered: Arc<dyn ExecutionPlan>,
259    /// Right streamed execution plan
260    pub streamed: Arc<dyn ExecutionPlan>,
261    /// The two expressions being compared
262    pub on: (Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>),
263    /// Comparison operator in the range predicate
264    pub operator: Operator,
265    /// How the join is performed
266    pub join_type: JoinType,
267    /// The schema once the join is applied
268    schema: SchemaRef,
269    /// Buffered data
270    buffered_fut: OnceAsync<BufferedSideData>,
271    /// Execution metrics
272    metrics: ExecutionPlanMetricsSet,
273
274    /// Sort expressions - See above for more details [`PiecewiseMergeJoinExec`]
275    ///
276    /// The left sort order, descending for `<`, `<=` operations + ascending for `>`, `>=` operations
277    left_child_plan_required_order: LexOrdering,
278    /// The right sort order, descending for `<`, `<=` operations + ascending for `>`, `>=` operations
279    /// Unsorted for mark joins
280    right_batch_required_orders: LexOrdering,
281
282    /// This determines the sort order of all join columns used in sorting the stream and buffered execution plans.
283    sort_options: SortOptions,
284    /// Cache holding plan properties like equivalences, output partitioning etc.
285    cache: Arc<PlanProperties>,
286    /// Number of partitions to process
287    num_partitions: usize,
288}
289
290impl PiecewiseMergeJoinExec {
291    pub fn try_new(
292        buffered: Arc<dyn ExecutionPlan>,
293        streamed: Arc<dyn ExecutionPlan>,
294        on: (Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>),
295        operator: Operator,
296        join_type: JoinType,
297        num_partitions: usize,
298    ) -> Result<Self> {
299        // TODO: Implement existence joins for PiecewiseMergeJoin
300        if is_existence_join(join_type) {
301            return not_impl_err!(
302                "Existence Joins are currently not supported for PiecewiseMergeJoin"
303            );
304        }
305
306        // Take the operator and enforce a sort order on the streamed + buffered side based on
307        // the operator type.
308        let sort_options = match operator {
309            Operator::Lt | Operator::LtEq => {
310                // For left existence joins the inputs will be swapped so the sort
311                // options are switched
312                if is_right_existence_join(join_type) {
313                    SortOptions::new(false, true)
314                } else {
315                    SortOptions::new(true, true)
316                }
317            }
318            Operator::Gt | Operator::GtEq => {
319                if is_right_existence_join(join_type) {
320                    SortOptions::new(true, true)
321                } else {
322                    SortOptions::new(false, true)
323                }
324            }
325            _ => {
326                return internal_err!(
327                    "Cannot contain non-range operator in PiecewiseMergeJoinExec"
328                );
329            }
330        };
331
332        // Give the same `sort_option for comparison later`
333        let left_child_plan_required_order =
334            vec![PhysicalSortExpr::new(Arc::clone(&on.0), sort_options)];
335        let right_batch_required_orders =
336            vec![PhysicalSortExpr::new(Arc::clone(&on.1), sort_options)];
337
338        let Some(left_child_plan_required_order) =
339            LexOrdering::new(left_child_plan_required_order)
340        else {
341            return internal_err!(
342                "PiecewiseMergeJoinExec requires valid sort expressions for its left side"
343            );
344        };
345        let Some(right_batch_required_orders) =
346            LexOrdering::new(right_batch_required_orders)
347        else {
348            return internal_err!(
349                "PiecewiseMergeJoinExec requires valid sort expressions for its right side"
350            );
351        };
352
353        let buffered_schema = buffered.schema();
354        let streamed_schema = streamed.schema();
355
356        // Create output schema for the join
357        let schema =
358            Arc::new(build_join_schema(&buffered_schema, &streamed_schema, &join_type).0);
359        let cache = Self::compute_properties(
360            &buffered,
361            &streamed,
362            Arc::clone(&schema),
363            join_type,
364            &on,
365        )?;
366
367        Ok(Self {
368            streamed,
369            buffered,
370            on,
371            operator,
372            join_type,
373            schema,
374            buffered_fut: Default::default(),
375            metrics: ExecutionPlanMetricsSet::new(),
376            left_child_plan_required_order,
377            right_batch_required_orders,
378            sort_options,
379            cache: Arc::new(cache),
380            num_partitions,
381        })
382    }
383
384    /// Reference to buffered side execution plan
385    pub fn buffered(&self) -> &Arc<dyn ExecutionPlan> {
386        &self.buffered
387    }
388
389    /// Reference to streamed side execution plan
390    pub fn streamed(&self) -> &Arc<dyn ExecutionPlan> {
391        &self.streamed
392    }
393
394    /// Join type
395    pub fn join_type(&self) -> JoinType {
396        self.join_type
397    }
398
399    /// Reference to sort options
400    pub fn sort_options(&self) -> &SortOptions {
401        &self.sort_options
402    }
403
404    /// Get probe side (streamed side) for the PiecewiseMergeJoin
405    /// In current implementation, probe side is determined according to join type.
406    pub fn probe_side(join_type: &JoinType) -> JoinSide {
407        match join_type {
408            JoinType::Right
409            | JoinType::Inner
410            | JoinType::Full
411            | JoinType::RightSemi
412            | JoinType::RightAnti
413            | JoinType::RightMark => JoinSide::Right,
414            JoinType::Left
415            | JoinType::LeftAnti
416            | JoinType::LeftSemi
417            | JoinType::LeftMark => JoinSide::Left,
418        }
419    }
420
421    pub fn compute_properties(
422        buffered: &Arc<dyn ExecutionPlan>,
423        streamed: &Arc<dyn ExecutionPlan>,
424        schema: SchemaRef,
425        join_type: JoinType,
426        join_on: &(PhysicalExprRef, PhysicalExprRef),
427    ) -> Result<PlanProperties> {
428        let eq_properties = join_equivalence_properties(
429            buffered.equivalence_properties().clone(),
430            streamed.equivalence_properties().clone(),
431            &join_type,
432            schema,
433            &Self::maintains_input_order(join_type),
434            Some(Self::probe_side(&join_type)),
435            std::slice::from_ref(join_on),
436        )?;
437
438        let output_partitioning =
439            asymmetric_join_output_partitioning(buffered, streamed, &join_type)?;
440
441        Ok(PlanProperties::new(
442            eq_properties,
443            output_partitioning,
444            EmissionType::Incremental,
445            boundedness_from_children([buffered, streamed]),
446        ))
447    }
448
449    // TODO: Add input order. Now they're all `false` indicating it will not maintain the input order.
450    // However, for certain join types the order is maintained. This can be updated in the future after
451    // more testing.
452    fn maintains_input_order(join_type: JoinType) -> Vec<bool> {
453        match join_type {
454            // The existence side is expected to come in sorted
455            JoinType::LeftSemi | JoinType::LeftAnti | JoinType::LeftMark => {
456                vec![false, false]
457            }
458            JoinType::RightSemi | JoinType::RightAnti | JoinType::RightMark => {
459                vec![false, false]
460            }
461            // Left, Right, Full, Inner Join is not guaranteed to maintain
462            // input order as the streamed side will be sorted during
463            // execution for `PiecewiseMergeJoin`
464            _ => vec![false, false],
465        }
466    }
467
468    // TODO
469    pub fn swap_inputs(&self) -> Result<Arc<dyn ExecutionPlan>> {
470        todo!()
471    }
472}
473
474impl ExecutionPlan for PiecewiseMergeJoinExec {
475    fn name(&self) -> &str {
476        "PiecewiseMergeJoinExec"
477    }
478
479    fn properties(&self) -> &Arc<PlanProperties> {
480        &self.cache
481    }
482
483    fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
484        vec![&self.buffered, &self.streamed]
485    }
486
487    fn apply_expressions(
488        &self,
489        f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
490    ) -> Result<TreeNodeRecursion> {
491        // Apply to the two expressions being compared in the range predicate
492        crate::apply_expression_roots([&self.on.0, &self.on.1], f)
493    }
494
495    fn required_input_distribution(&self) -> Vec<Distribution> {
496        self.input_distribution_requirements().into_per_child()
497    }
498
499    fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
500        crate::InputDistributionRequirements::new(vec![
501            Distribution::SinglePartition,
502            Distribution::UnspecifiedDistribution,
503        ])
504    }
505
506    fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
507        // Existence joins don't need to be sorted on one side.
508        if is_right_existence_join(self.join_type) {
509            unimplemented!()
510        } else {
511            // Sort the right side in memory, so we do not need to enforce any sorting
512            vec![
513                Some(OrderingRequirements::from(
514                    self.left_child_plan_required_order.clone(),
515                )),
516                None,
517            ]
518        }
519    }
520
521    fn replace_children(
522        self: Arc<Self>,
523        mut children: Vec<Arc<dyn ExecutionPlan>>,
524        options: ReplaceChildrenOptions,
525    ) -> Result<Arc<dyn ExecutionPlan>> {
526        validate_child_count!(self, children);
527        match options.children_properties {
528            ChildrenPropertiesMode::Keep => {
529                let buffered = children.swap_remove(0);
530                let streamed = children.swap_remove(0);
531                Ok(Arc::new(Self {
532                    buffered,
533                    streamed,
534                    on: self.on.clone(),
535                    operator: self.operator,
536                    join_type: self.join_type,
537                    schema: Arc::clone(&self.schema),
538                    left_child_plan_required_order: self
539                        .left_child_plan_required_order
540                        .clone(),
541                    right_batch_required_orders: self.right_batch_required_orders.clone(),
542                    sort_options: self.sort_options,
543                    cache: Arc::clone(&self.cache),
544                    num_partitions: self.num_partitions,
545
546                    // Re-set state.
547                    metrics: ExecutionPlanMetricsSet::new(),
548                    buffered_fut: Default::default(),
549                }))
550            }
551            ChildrenPropertiesMode::Recompute => match &children[..] {
552                [left, right] => Ok(Arc::new(PiecewiseMergeJoinExec::try_new(
553                    Arc::clone(left),
554                    Arc::clone(right),
555                    self.on.clone(),
556                    self.operator,
557                    self.join_type,
558                    self.num_partitions,
559                )?)),
560                _ => internal_err!(
561                    "PiecewiseMergeJoin should have 2 children, found {}",
562                    children.len()
563                ),
564            },
565        }
566    }
567
568    fn with_new_children(
569        self: Arc<Self>,
570        children: Vec<Arc<dyn ExecutionPlan>>,
571    ) -> Result<Arc<dyn ExecutionPlan>> {
572        self.replace_children(
573            children,
574            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
575        )
576    }
577
578    fn with_new_children_and_same_properties(
579        self: Arc<Self>,
580        children: Vec<Arc<dyn ExecutionPlan>>,
581    ) -> Result<Arc<dyn ExecutionPlan>> {
582        self.replace_children(
583            children,
584            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
585        )
586    }
587
588    fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
589        let buffered = Arc::clone(&self.buffered);
590        let streamed = Arc::clone(&self.streamed);
591        self.replace_children(
592            vec![buffered, streamed],
593            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
594        )
595    }
596
597    fn execute(
598        &self,
599        partition: usize,
600        context: Arc<datafusion_execution::TaskContext>,
601    ) -> Result<SendableRecordBatchStream> {
602        let on_buffered = Arc::clone(&self.on.0);
603        let on_streamed = Arc::clone(&self.on.1);
604
605        let metrics = BuildProbeJoinMetrics::new(partition, &self.metrics);
606        let buffered_fut = self.buffered_fut.try_once(|| {
607            let reservation = MemoryConsumer::new("PiecewiseMergeJoinInput")
608                .register(context.memory_pool());
609
610            let buffered_stream = self.buffered.execute(0, Arc::clone(&context))?;
611            Ok(build_buffered_data(
612                buffered_stream,
613                Arc::clone(&on_buffered),
614                metrics.clone(),
615                reservation,
616                build_visited_indices_map(self.join_type),
617                self.num_partitions,
618            ))
619        })?;
620
621        let streamed = self.streamed.execute(partition, Arc::clone(&context))?;
622
623        let batch_size = context.session_config().batch_size();
624
625        // TODO: Add existence joins + this is guarded at physical planner
626        if is_existence_join(self.join_type()) {
627            unreachable!()
628        } else {
629            Ok(Box::pin(ClassicPWMJStream::try_new(
630                Arc::clone(&self.schema),
631                on_streamed,
632                self.join_type,
633                self.operator,
634                streamed,
635                BufferedSide::Initial(BufferedSideInitialState { buffered_fut }),
636                PiecewiseMergeJoinStreamState::WaitBufferedSide,
637                self.sort_options,
638                metrics,
639                batch_size,
640            )))
641        }
642    }
643
644    fn metrics(&self) -> Option<MetricsSet> {
645        Some(self.metrics.clone_inner())
646    }
647}
648
649impl DisplayAs for PiecewiseMergeJoinExec {
650    fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> std::fmt::Result {
651        let on_str = format!(
652            "({} {} {})",
653            fmt_sql(self.on.0.as_ref()),
654            self.operator,
655            fmt_sql(self.on.1.as_ref())
656        );
657
658        match t {
659            DisplayFormatType::Default | DisplayFormatType::Verbose => {
660                write!(
661                    f,
662                    "PiecewiseMergeJoin: operator={:?}, join_type={:?}, on={}",
663                    self.operator, self.join_type, on_str
664                )
665            }
666
667            DisplayFormatType::TreeRender => {
668                writeln!(f, "operator={:?}", self.operator)?;
669                if self.join_type != JoinType::Inner {
670                    writeln!(f, "join_type={:?}", self.join_type)?;
671                }
672                writeln!(f, "on={on_str}")
673            }
674        }
675    }
676}
677
678async fn build_buffered_data(
679    buffered: SendableRecordBatchStream,
680    on_buffered: PhysicalExprRef,
681    metrics: BuildProbeJoinMetrics,
682    reservation: MemoryReservation,
683    build_map: bool,
684    remaining_partitions: usize,
685) -> Result<BufferedSideData> {
686    let schema = buffered.schema();
687
688    // Combine batches and record number of rows
689    let initial = (Vec::new(), 0, metrics, reservation);
690    let (batches, num_rows, metrics, reservation) = buffered
691        .try_fold(initial, |mut acc, batch| async {
692            let batch_size = get_record_batch_memory_size(&batch);
693            acc.3.try_grow(batch_size)?;
694            acc.2.build_mem_used.add(batch_size);
695            acc.2.build_input_batches.add(1);
696            acc.2.build_input_rows.add(batch.num_rows());
697            // Update row count
698            acc.1 += batch.num_rows();
699            // Push batch to output
700            acc.0.push(batch);
701            Ok(acc)
702        })
703        .await?;
704
705    let single_batch = concat_batches(&schema, batches.iter())?;
706
707    // Evaluate physical expression on the buffered side.
708    let buffered_values = on_buffered
709        .evaluate(&single_batch)?
710        .into_array(single_batch.num_rows())?;
711
712    // We add the single batch size + the memory of the join keys
713    // size of the size estimation
714    let size_estimation = get_record_batch_memory_size(&single_batch)
715        + buffered_values.get_array_memory_size();
716    reservation.try_grow(size_estimation)?;
717    metrics.build_mem_used.add(size_estimation);
718
719    // Created visited indices bitmap only if the join type requires it
720    let visited_indices_bitmap = if build_map {
721        let bitmap_size = bit_util::ceil(single_batch.num_rows(), 8);
722        reservation.try_grow(bitmap_size)?;
723        metrics.build_mem_used.add(bitmap_size);
724
725        let mut bitmap_buffer = BooleanBufferBuilder::new(single_batch.num_rows());
726        bitmap_buffer.append_n(num_rows, false);
727        bitmap_buffer
728    } else {
729        BooleanBufferBuilder::new(0)
730    };
731
732    let buffered_data = BufferedSideData::new(
733        single_batch,
734        buffered_values,
735        Mutex::new(visited_indices_bitmap),
736        remaining_partitions,
737        reservation,
738    );
739
740    Ok(buffered_data)
741}
742
743pub(super) struct BufferedSideData {
744    pub(super) batch: RecordBatch,
745    values: ArrayRef,
746    pub(super) visited_indices_bitmap: SharedBitmapBuilder,
747    pub(super) remaining_partitions: AtomicUsize,
748    _reservation: MemoryReservation,
749}
750
751impl BufferedSideData {
752    pub(super) fn new(
753        batch: RecordBatch,
754        values: ArrayRef,
755        visited_indices_bitmap: SharedBitmapBuilder,
756        remaining_partitions: usize,
757        reservation: MemoryReservation,
758    ) -> Self {
759        Self {
760            batch,
761            values,
762            visited_indices_bitmap,
763            remaining_partitions: AtomicUsize::new(remaining_partitions),
764            _reservation: reservation,
765        }
766    }
767
768    pub(super) fn batch(&self) -> &RecordBatch {
769        &self.batch
770    }
771
772    pub(super) fn values(&self) -> &ArrayRef {
773        &self.values
774    }
775}
776
777pub(super) enum BufferedSide {
778    /// Indicates that build-side not collected yet
779    Initial(BufferedSideInitialState),
780    /// Indicates that build-side data has been collected
781    Ready(BufferedSideReadyState),
782}
783
784impl BufferedSide {
785    // Takes a mutable state of the buffered row batches
786    pub(super) fn try_as_initial_mut(&mut self) -> Result<&mut BufferedSideInitialState> {
787        match self {
788            BufferedSide::Initial(state) => Ok(state),
789            _ => internal_err!("Expected build side in initial state"),
790        }
791    }
792
793    pub(super) fn try_as_ready(&self) -> Result<&BufferedSideReadyState> {
794        match self {
795            BufferedSide::Ready(state) => Ok(state),
796            _ => {
797                internal_err!("Expected build side in ready state")
798            }
799        }
800    }
801
802    /// Tries to extract BuildSideReadyState from BuildSide enum.
803    /// Returns an error if state is not Ready.
804    pub(super) fn try_as_ready_mut(&mut self) -> Result<&mut BufferedSideReadyState> {
805        match self {
806            BufferedSide::Ready(state) => Ok(state),
807            _ => internal_err!("Expected build side in ready state"),
808        }
809    }
810}
811
812pub(super) struct BufferedSideInitialState {
813    pub(crate) buffered_fut: OnceFut<BufferedSideData>,
814}
815
816pub(super) struct BufferedSideReadyState {
817    /// Collected build-side data
818    pub(super) buffered_data: Arc<BufferedSideData>,
819}