## Coverage Report

#### Created: 2025-11-22 06:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /Users/andrewlamb/Software/datafusion/datafusion/common/src/join_type.rs

    Line
    Count
    Source
    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
    18
    //! Defines the [`JoinType`], [`JoinConstraint`] and [`JoinSide`] types.
    19
    20
    use std::{
    21
        fmt::{self, Display, Formatter},
    22
        str::FromStr,
    23
    };
    24
    25
    use crate::error::_not_impl_err;
    26
    use crate::{DataFusionError, Result};
    27
    28
    /// Join type
    29
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
    30
    pub enum JoinType {
    31
        /// Inner Join - Returns only rows where there is a matching value in both tables based on the join condition.
    32
        /// For example, if joining table A and B on A.id = B.id, only rows where A.id equals B.id will be included.
    33
        /// All columns from both tables are returned for the matching rows. Non-matching rows are excluded entirely.
    34
        Inner,
    35
        /// Left Join - Returns all rows from the left table and matching rows from the right table.
    36
        /// If no match, NULL values are returned for columns from the right table.
    37
        Left,
    38
        /// Right Join - Returns all rows from the right table and matching rows from the left table.
    39
        /// If no match, NULL values are returned for columns from the left table.
    40
        Right,
    41
        /// Full Join (also called Full Outer Join) - Returns all rows from both tables, matching rows where possible.
    42
        /// When a row from either table has no match in the other table, the missing columns are filled with NULL values.
    43
        /// For example, if table A has row X with no match in table B, the result will contain row X with NULL values for all of table B's columns.
    44
        /// This join type preserves all records from both tables, making it useful when you need to see all data regardless of matches.
    45
        Full,
    46
        /// Left Semi Join - Returns rows from the left table that have matching rows in the right table.
    47
        /// Only columns from the left table are returned.
    48
        LeftSemi,
    49
        /// Right Semi Join - Returns rows from the right table that have matching rows in the left table.
    50
        /// Only columns from the right table are returned.
    51
        RightSemi,
    52
        /// Left Anti Join - Returns rows from the left table that do not have a matching row in the right table.
    53
        LeftAnti,
    54
        /// Right Anti Join - Returns rows from the right table that do not have a matching row in the left table.
    55
        RightAnti,
    56
        /// Left Mark join
    57
        ///
    58
        /// Returns one record for each record from the left input. The output contains an additional
    59
        /// column "mark" which is true if there is at least one match in the right input where the
    60
        /// join condition evaluates to true. Otherwise, the mark column is false. For more details see
    61
        /// [1]. This join type is used to decorrelate EXISTS subqueries used inside disjunctive
    62
        /// predicates.
    63
        ///
    64
        /// Note: This we currently do not implement the full null semantics for the mark join described
    65
        /// in [1] which will be needed if we and ANY subqueries. In our version the mark column will
    66
        /// only be true for had a match and false when no match was found, never null.
    67
        ///
    68
        /// [1]: http://btw2017.informatik.uni-stuttgart.de/slidesandpapers/F1-10-37/paper_web.pdf
    69
        LeftMark,
    70
        /// Right Mark Join
    71
        ///
    72
        /// Same logic as the LeftMark Join above, however it returns a record for each record from the
    73
        /// right input.
    74
        RightMark,
    75
    }
    76
    77
    impl JoinType {
    78
    0
        pub fn is_outer(self) -> bool {
    79
    0
            self == JoinType::Left || self == JoinType::Right || self == JoinType::Full
    80
    0
        }
    81
    82
        /// Returns the `JoinType` if the (2) inputs were swapped
    83
        ///
    84
        /// Panics if [`Self::supports_swap`] returns false
    85
    0
        pub fn swap(&self) -> JoinType {
    86
    0
            match self {
    87
    0
                JoinType::Inner => JoinType::Inner,
    88
    0
                JoinType::Full => JoinType::Full,
    89
    0
                JoinType::Left => JoinType::Right,
    90
    0
                JoinType::Right => JoinType::Left,
    91
    0
                JoinType::LeftSemi => JoinType::RightSemi,
    92
    0
                JoinType::RightSemi => JoinType::LeftSemi,
    93
    0
                JoinType::LeftAnti => JoinType::RightAnti,
    94
    0
                JoinType::RightAnti => JoinType::LeftAnti,
    95
    0
                JoinType::LeftMark => JoinType::RightMark,
    96
    0
                JoinType::RightMark => JoinType::LeftMark,
    97
            }
    98
    0
        }
    99
    100
        /// Does the join type support swapping inputs?
    101
    0
        pub fn supports_swap(&self) -> bool {
    102
    0
            matches!(
    103
    0
                self,
    104
                JoinType::Inner
    105
                    | JoinType::Left
    106
                    | JoinType::Right
    107
                    | JoinType::Full
    108
                    | JoinType::LeftSemi
    109
                    | JoinType::RightSemi
    110
                    | JoinType::LeftAnti
    111
                    | JoinType::RightAnti
    112
                    | JoinType::LeftMark
    113
                    | JoinType::RightMark
    114
            )
    115
    0
        }
    116
    }
    117
    118
    impl Display for JoinType {
    119
    0
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
    120
    0
            let join_type = match self {
    121
    0
                JoinType::Inner => "Inner",
    122
    0
                JoinType::Left => "Left",
    123
    0
                JoinType::Right => "Right",
    124
    0
                JoinType::Full => "Full",
    125
    0
                JoinType::LeftSemi => "LeftSemi",
    126
    0
                JoinType::RightSemi => "RightSemi",
    127
    0
                JoinType::LeftAnti => "LeftAnti",
    128
    0
                JoinType::RightAnti => "RightAnti",
    129
    0
                JoinType::LeftMark => "LeftMark",
    130
    0
                JoinType::RightMark => "RightMark",
    131
            };
    132
    0
            write!(f, "{join_type}")
    133
    0
        }
    134
    }
    135
    136
    impl FromStr for JoinType {
    137
        type Err = DataFusionError;
    138
    139
    0
        fn from_str(s: &str) -> Result<Self> {
    140
    0
            let s = s.to_uppercase();
    141
    0
            match s.as_str() {
    142
    0
                "INNER" => Ok(JoinType::Inner),
    143
    0
                "LEFT" => Ok(JoinType::Left),
    144
    0
                "RIGHT" => Ok(JoinType::Right),
    145
    0
                "FULL" => Ok(JoinType::Full),
    146
    0
                "LEFTSEMI" => Ok(JoinType::LeftSemi),
    147
    0
                "RIGHTSEMI" => Ok(JoinType::RightSemi),
    148
    0
                "LEFTANTI" => Ok(JoinType::LeftAnti),
    149
    0
                "RIGHTANTI" => Ok(JoinType::RightAnti),
    150
    0
                "LEFTMARK" => Ok(JoinType::LeftMark),
    151
    0
                "RIGHTMARK" => Ok(JoinType::RightMark),
    152
    0
                _ => _not_impl_err!("The join type {s} does not exist or is not implemented"),
    153
            }
    154
    0
        }
    155
    }
    156
    157
    /// Join constraint
    158
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
    159
    pub enum JoinConstraint {
    160
        /// Join ON
    161
        On,
    162
        /// Join USING
    163
        Using,
    164
    }
    165
    166
    impl Display for JoinSide {
    167
    0
        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    168
    0
            match self {
    169
    0
                JoinSide::Left => write!(f, "left"),
    170
    0
                JoinSide::Right => write!(f, "right"),
    171
    0
                JoinSide::None => write!(f, "none"),
    172
            }
    173
    0
        }
    174
    }
    175
    176
    /// Join side.
    177
    /// Stores the referred table side during calculations
    178
    #[derive(Debug, Clone, Copy, PartialEq)]
    179
    pub enum JoinSide {
    180
        /// Left side of the join
    181
        Left,
    182
        /// Right side of the join
    183
        Right,
    184
        /// Neither side of the join, used for Mark joins where the mark column does not belong to
    185
        /// either side of the join
    186
        None,
    187
    }
    188
    189
    impl JoinSide {
    190
        /// Inverse the join side
    191
    0
        pub fn negate(&self) -> Self {
    192
    0
            match self {
    193
    0
                JoinSide::Left => JoinSide::Right,
    194
    0
                JoinSide::Right => JoinSide::Left,
    195
    0
                JoinSide::None => JoinSide::None,
    196
            }
    197
    0
        }
    198
    }