Determines the join implementation for join operators.
(
&self,
relation: &mut MirRelationExpr,
mfp_above: MapFilterProject,
indexes: &IndexMap,
stats: &dyn StatisticsOracle,
features: &OptimizerFeatures,
| 128 | |
| 129 | /// Determines the join implementation for join operators. |
| 130 | pub fn action( |
| 131 | &self, |
| 132 | relation: &mut MirRelationExpr, |
| 133 | mfp_above: MapFilterProject, |
| 134 | indexes: &IndexMap, |
| 135 | stats: &dyn StatisticsOracle, |
| 136 | features: &OptimizerFeatures, |
| 137 | ) -> Result<(), TransformError> { |
| 138 | if let MirRelationExpr::Join { |
| 139 | inputs, |
| 140 | equivalences, |
| 141 | // (Note that `JoinImplementation` runs in a fixpoint loop.) |
| 142 | // If the current implementation is |
| 143 | // - Unimplemented, then we need to come up with an implementation. |
| 144 | // - Differential, then we consider switching to a Delta join, because we might have |
| 145 | // inserted some ArrangeBys that create new arrangements when we came up with the |
| 146 | // Differential plan, in which case a Delta join might have become viable. |
| 147 | // - Delta, then we are good already. |
| 148 | // - IndexedFilter, then we just leave that alone, because those are out of scope |
| 149 | // for JoinImplementation (they are created by `LiteralConstraints`). |
| 150 | // We don't want to change from a Differential plan to an other Differential plan, or |
| 151 | // from a Delta plan to an other Delta plan, because the second run cannot distinguish |
| 152 | // between an ArrangeBy that marks an already existing arrangement and an ArrangeBy |
| 153 | // that was inserted by a previous run of JoinImplementation. (We should eventually |
| 154 | // refactor this to make ArrangeBy unambiguous somehow. Maybe move JoinImplementation |
| 155 | // to the lowering.) |
| 156 | implementation: implementation @ (Unimplemented | Differential(..)), |
| 157 | } = relation |
| 158 | { |
| 159 | // If we eagerly plan delta joins, we don't need the second run to "pick up" delta joins |
| 160 | // that could be planned with the arrangements from a differential. If such a delta |
| 161 | // join were viable, we'd have already planned it the first time. |
| 162 | if features.enable_eager_delta_joins && !matches!(implementation, Unimplemented) { |
| 163 | return Ok(()); |
| 164 | } |
| 165 | |
| 166 | let input_types = inputs.iter().map(|i| i.typ()).collect::<Vec<_>>(); |
| 167 | |
| 168 | // Canonicalize the equivalence classes |
| 169 | if matches!(implementation, Unimplemented) { |
| 170 | // Let's do this only if it's the first run of JoinImplementation, in which case we |
| 171 | // are guaranteed to produce a new plan, which will be compatible with the modified |
| 172 | // equivalences from the below call. Otherwise, if we already have a Differential or |
| 173 | // a Delta join, then we might discard the new plan and go with the old plan, which |
| 174 | // was created previously for the old equivalences, and might be invalid for the |
| 175 | // modified equivalences from the below call. Note that this issue can arise only if |
| 176 | // `canonicalize_equivalences` is not idempotent, which unfortunately seems to be |
| 177 | // the case. |
| 178 | mz_expr::canonicalize::canonicalize_equivalences( |
| 179 | equivalences, |
| 180 | input_types.iter().map(|t| &t.column_types), |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | // Common information of broad utility. |
| 185 | let input_mapper = JoinInputMapper::new_from_input_types(&input_types); |
| 186 | |
| 187 | // The first fundamental question is whether we should employ a delta query or not. |
no test coverage detected