pub fn join(inputs: Vec<MirRelationExpr>, variables: Vec<Vec<(usize, usize)>>) -> Self {
        let input_mapper = join_input_mapper::JoinInputMapper::new(&inputs);

        let equivalences = variables
            .into_iter()
            .map(|vs| {
                vs.into_iter()
                    .map(|(r, c)| input_mapper.map_expr_to_global(MirScalarExpr::Column(c), r))
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>();

        Self::join_scalars(inputs, equivalences)
    }
/// Performs a relational equijoin among the input collections.
///
/// The sequence `inputs` each describe different input collections, and the sequence `variables` describes
/// equality constraints that some of their columns must satisfy. Each element in `variable` describes a set
/// of pairs  `(input_index, column_index)` where every value described by that set must be equal.
///
/// For example, the pair `(input, column)` indexes into `inputs[input][column]`, extracting the `input`th
/// input collection and for each row examining its `column`th column.
///
/// # Example
///
/// ```rust
/// use repr::{Datum, ColumnType, RelationType, ScalarType};
/// use expr::MirRelationExpr;
///
/// // A common schema for each input.
/// let schema = RelationType::new(vec![
///     ScalarType::Int32.nullable(false),
///     ScalarType::Int32.nullable(false),
/// ]);
///
/// // the specific data are not important here.
/// let data = vec![Datum::Int32(0), Datum::Int32(1)];
///
/// // Three collections that could have been different.
/// let input0 = MirRelationExpr::constant(vec![data.clone()], schema.clone());
/// let input1 = MirRelationExpr::constant(vec![data.clone()], schema.clone());
/// let input2 = MirRelationExpr::constant(vec![data.clone()], schema.clone());
///
/// // Join the three relations looking for triangles, like so.
/// //
/// //     Output(A,B,C) := Input0(A,B), Input1(B,C), Input2(A,C)
/// let joined = MirRelationExpr::join(
///     vec![input0, input1, input2],
///     vec![
///         vec![(0,0), (2,0)], // fields A of inputs 0 and 2.
///         vec![(0,1), (1,0)], // fields B of inputs 0 and 1.
///         vec![(1,1), (2,1)], // fields C of inputs 1 and 2.
///     ],
/// );
///
/// // Technically the above produces `Output(A,B,B,C,A,C)` because the columns are concatenated.
/// // A projection resolves this and produces the correct output.
/// let result = joined.project(vec![0, 1, 3]);
/// ```
Performs a relational equijoin among the input collections.
The sequence `inputs` each describe different input collections, and the sequence `variables` describes
equality constraints that some of their columns must satisfy. Each element in `variable` describes a set
of pairs  `(input_index, column_index)` where every value described by that set must be equal.


Performs a relational equijoin among the input collections . The sequence ` inputs ` each describe different input collections and the sequence ` variables ` describes equality constraints that some of their columns must satisfy . Each element in ` variable ` describes a set of pairs ` ( input_index column_index ) ` where every value described by that set must be equal .
pub fn join(inputs: Vec<MirRelationExpr>, variables: Vec<Vec<(usize, usize)>>) -> Self {
        let input_mapper = join_input_mapper::JoinInputMapper::new(&inputs);
        let equivalences = variables
            .into_iter()
            .map(|vs| {
                vs.into_iter()
                    .map(|(r, c)| input_mapper.map_expr_to_global(MirScalarExpr::Column(c), r))
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>();
        Self::join_scalars(inputs, equivalences)
    }
pub fn join ( inputs : Vec < MirRelationExpr > , variables : Vec < Vec < ( usize , usize ) > > ) -> Self { let input_mapper = join_input_mapper :: JoinInputMapper :: new ( & inputs ) ; let equivalences = variables . into_iter ( ) . map ( | vs | { vs . into_iter ( ) . map ( | ( r , c ) | input_mapper . map_expr_to_global ( MirScalarExpr :: Column ( c ) , r ) ) . collect :: < Vec < _ > > ( ) } ) . collect :: < Vec < _ > > ( ) ; Self :: join_scalars ( inputs , equivalences ) }
Performs a relational equijoin among the input collections.
Performs a relational equijoin among the input collections .
inputs
Vec<MirRelationExpr>
variables
Vec<Vec<(usize, usize)>>
inputs
Vec<MirRelationExpr>
variables
Vec<Vec<(usize, usize)>>
example
```rust
use repr::{Datum, ColumnType, RelationType, ScalarType};
use expr::MirRelationExpr.

A common schema for each input.
let schema = RelationType::new(vec![
ScalarType::Int32.nullable(false),
ScalarType::Int32.nullable(false),
]).

the specific data are not important here.

Three collections that could have been different.
let input0 = MirRelationExpr::constant(vec![data.clone()], schema.clone());
let input1 = MirRelationExpr::constant(vec![data.clone()], schema.clone());
let input2 = MirRelationExpr::constant(vec![data.clone()], schema.clone()).

Join the three relations looking for triangles, like so.



Technically the above produces `Output(A,B,B,C,A,C)` because the columns are concatenated.
A projection resolves this and produces the correct output.
` ` ` rust use repr :: { Datum ColumnType RelationType ScalarType } ; use expr :: MirRelationExpr . A common schema for each input . let schema = RelationType :: new ( vec! [ ScalarType :: Int32 . nullable ( false ) ScalarType :: Int32 . nullable ( false ) ] ) . the specific data are not important here . Three collections that could have been different . let input0 = MirRelationExpr :: constant ( vec! [ data . clone () ] schema . clone () ) ; let input1 = MirRelationExpr :: constant ( vec! [ data . clone () ] schema . clone () ) ; let input2 = MirRelationExpr :: constant ( vec! [ data . clone () ] schema . clone () ) . Join the three relations looking for triangles like so . Technically the above produces ` Output ( A B B C A C ) ` because the columns are concatenated . A projection resolves this and produces the correct output .