pub struct SymmetricHashJoinExec { /* private fields */ }
Expand description

A symmetric hash join with range conditions is when both streams are hashed on the join key and the resulting hash tables are used to join the streams. The join is considered symmetric because the hash table is built on the join keys from both streams, and the matching of rows is based on the values of the join keys in both streams. This type of join is efficient in streaming context as it allows for fast lookups in the hash table, rather than having to scan through one or both of the streams to find matching rows, also it only considers the elements from the stream that fall within a certain sliding window (w/ range conditions), making it more efficient and less likely to store stale data. This enables operating on unbounded streaming data without any memory issues.

For each input stream, create a hash table.

  • For each new RecordBatch in build side, hash and insert into inputs hash table. Update offsets.
  • Test if input is equal to a predefined set of other inputs.
  • If so record the visited rows. If the matched row results must be produced (INNER, LEFT), output the RecordBatch.
  • Try to prune other side (probe) with new RecordBatch.
  • If the join type indicates that the unmatched rows results must be produced (LEFT, FULL etc.), output the RecordBatch when a pruning happens or at the end of the data.
                       +-------------------------+
                       |                         |
  left stream ---------|  Left OneSideHashJoiner |---+
                       |                         |   |
                       +-------------------------+   |
                                                     |
                                                     |--------- Joined output
                                                     |
                       +-------------------------+   |
                       |                         |   |
 right stream ---------| Right OneSideHashJoiner |---+
                       |                         |
                       +-------------------------+

Prune build side when the new RecordBatch comes to the probe side. We utilize interval arithmetic
on JoinFilter's sorted PhysicalExprs to calculate the joinable range.


              PROBE SIDE          BUILD SIDE
                BUFFER              BUFFER
            +-------------+     +------------+
            |             |     |            |    Unjoinable
            |             |     |            |    Range
            |             |     |            |
            |             |  |---------------------------------
            |             |  |  |            |
            |             |  |  |            |
            |             | /   |            |
            |             | |   |            |
            |             | |   |            |
            |             | |   |            |
            |             | |   |            |
            |             | |   |            |    Joinable
            |             |/    |            |    Range
            |             ||    |            |
            |+-----------+||    |            |
            || Record    ||     |            |
            || Batch     ||     |            |
            |+-----------+||    |            |
            +-------------+\    +------------+
                            |
                            \
                             |---------------------------------

 This happens when range conditions are provided on sorted columns. E.g.

       SELECT * FROM left_table, right_table
       ON
         left_key = right_key AND
         left_time > right_time - INTERVAL 12 MINUTES AND left_time < right_time + INTERVAL 2 HOUR

or
      SELECT * FROM left_table, right_table
       ON
         left_key = right_key AND
         left_sorted > right_sorted - 3 AND left_sorted < right_sorted + 10

For general purpose, in the second scenario, when the new data comes to probe side, the conditions can be used to
determine a specific threshold for discarding rows from the inner buffer. For example, if the sort order the
two columns ("left_sorted" and "right_sorted") are ascending (it can be different in another scenarios)
and the join condition is "left_sorted > right_sorted - 3" and the latest value on the right input is 1234, meaning
that the left side buffer must only keep rows where "leftTime > rightTime - 3 > 1234 - 3 > 1231" ,
making the smallest value in 'left_sorted' 1231 and any rows below (since ascending)
than that can be dropped from the inner buffer.

Implementations§

source§

impl SymmetricHashJoinExec

source

pub fn try_new( left: Arc<dyn ExecutionPlan>, right: Arc<dyn ExecutionPlan>, on: JoinOn, filter: Option<JoinFilter>, join_type: &JoinType, null_equals_null: bool ) -> Result<Self>

Tries to create a new SymmetricHashJoinExec.

Error

This function errors when:

  • It is not possible to join the left and right sides on keys on, or
  • It fails to construct SortedFilterExprs, or
  • It fails to create the ExprIntervalGraph.
source

pub fn left(&self) -> &Arc<dyn ExecutionPlan>

left stream

source

pub fn right(&self) -> &Arc<dyn ExecutionPlan>

right stream

source

pub fn on(&self) -> &[(Column, Column)]

Set of common columns used to join on

source

pub fn filter(&self) -> Option<&JoinFilter>

Filters applied before join output

source

pub fn join_type(&self) -> &JoinType

How the join is performed

source

pub fn null_equals_null(&self) -> bool

Get null_equals_null

source

pub fn check_if_order_information_available(&self) -> Result<bool>

Check if order information covers every column in the filter expression.

Trait Implementations§

source§

impl Debug for SymmetricHashJoinExec

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl ExecutionPlan for SymmetricHashJoinExec

source§

fn as_any(&self) -> &dyn Any

Returns the execution plan as Any so that it can be downcast to a specific implementation.
source§

fn schema(&self) -> SchemaRef

Get the schema for this execution plan
source§

fn unbounded_output(&self, children: &[bool]) -> Result<bool>

Specifies whether this plan generates an infinite stream of records. If the plan does not support pipelining, but its input(s) are infinite, returns an error to indicate this.
source§

fn benefits_from_input_partitioning(&self) -> bool

Returns true if this operator would benefit from partitioning its input (and thus from more parallelism). For operators that do very little work the overhead of extra parallelism may outweigh any benefits Read more
source§

fn required_input_distribution(&self) -> Vec<Distribution>

Specifies the data distribution requirements for all the children for this operator, By default it’s [Distribution::UnspecifiedDistribution] for each child,
source§

fn output_partitioning(&self) -> Partitioning

Specifies the output partitioning scheme of this plan
source§

fn output_ordering(&self) -> Option<&[PhysicalSortExpr]>

If the output of this operator within each partition is sorted, returns Some(keys) with the description of how it was sorted. Read more
source§

fn equivalence_properties(&self) -> EquivalenceProperties

Get the EquivalenceProperties within the plan
source§

fn children(&self) -> Vec<Arc<dyn ExecutionPlan>>

Get a list of child execution plans that provide the input for this plan. The returned list will be empty for leaf nodes, will contain a single value for unary nodes, or two values for binary nodes (such as joins).
source§

fn with_new_children( self: Arc<Self>, children: Vec<Arc<dyn ExecutionPlan>> ) -> Result<Arc<dyn ExecutionPlan>>

Returns a new plan where all children were replaced by new plans.
source§

fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter<'_>) -> Result

Format this ExecutionPlan to f in the specified type. Read more
source§

fn metrics(&self) -> Option<MetricsSet>

Return a snapshot of the set of Metrics for this ExecutionPlan. Read more
source§

fn statistics(&self) -> Statistics

Returns the global output statistics for this ExecutionPlan node.
source§

fn execute( &self, partition: usize, context: Arc<TaskContext> ) -> Result<SendableRecordBatchStream>

creates an iterator
source§

fn required_input_ordering(&self) -> Vec<Option<Vec<PhysicalSortRequirement>>>

Specifies the ordering requirements for all of the children For each child, it’s the local ordering requirement within each partition rather than the global ordering Read more
source§

fn maintains_input_order(&self) -> Vec<bool>

Returns false if this operator’s implementation may reorder rows within or between partitions. Read more
source§

fn ordering_equivalence_properties(&self) -> OrderingEquivalenceProperties

Get the OrderingEquivalenceProperties within the plan

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more