@@ -4228,12 +4228,49 @@ NodeMap FindReachableTablesFrom(NodeMap tables, const JoinHypergraph &graph) {
42284228 return reachable;
42294229}
42304230
4231- // Returns whether the given set of parameter tables is partially, but not
4232- // fully, resolved by joining towards the other side.
4233- bool PartiallyResolvedParameterization (NodeMap parameter_tables,
4234- NodeMap other_side) {
4235- return (parameter_tables & ~other_side) != 0 &&
4236- (parameter_tables & ~other_side) != parameter_tables;
4231+ /* *
4232+ Is it possible to resolve more parameter tables before performing a nested
4233+ loop join between "outer" and "inner", or will the join have to be performed
4234+ first?
4235+
4236+ In more precise terms:
4237+
4238+ Consider the set of parameters (a set of tables) that are left unresolved
4239+ after joining inner and outer. This function returns true if this set is
4240+ non-empty and at least one of these unresolved parameter tables, denoted by t,
4241+ can be joined directly into either outer or inner such that the result of
4242+ joining either {outer, t} with {inner} or {outer} with {inner, t} would end up
4243+ with more resolved parameters (fewer unresolved parameters) than simply
4244+ joining {outer} and {inner}.
4245+ */
4246+ bool CanResolveMoreParameterTables (NodeMap outer, NodeMap inner,
4247+ NodeMap outer_parameters,
4248+ NodeMap inner_parameters,
4249+ NodeMap outer_reachable,
4250+ NodeMap inner_reachable) {
4251+ const NodeMap unresolved_parameters =
4252+ (outer_parameters | inner_parameters) & ~(outer | inner);
4253+
4254+ if (unresolved_parameters == 0 ) {
4255+ // No unresolved parameters after joining outer and inner (so we cannot
4256+ // resolve more parameters by first joining in parameter tables).
4257+ return false ;
4258+ }
4259+
4260+ // Unresolved parameterizations on either side of the join can be resolved by
4261+ // joining a parameter table into the outer path first, if it's reachable.
4262+ if (Overlaps (unresolved_parameters, outer_reachable)) {
4263+ return true ;
4264+ }
4265+
4266+ // Unresolved parameterizations that are only on the inner path, can also be
4267+ // resolved by joining a parameter table to the inner path first, if it's
4268+ // reachable.
4269+ if (Overlaps (unresolved_parameters & ~outer_parameters, inner_reachable)) {
4270+ return true ;
4271+ }
4272+
4273+ return false ;
42374274}
42384275
42394276/* *
@@ -4245,40 +4282,72 @@ bool PartiallyResolvedParameterization(NodeMap parameter_tables,
42454282 plans to be left-deep (since such plans never gain anything from being
42464283 bushy), reducing the search space significantly without compromising
42474284 plan quality.
4285+
4286+ @param left_path An access path which joins together a superset of all the
4287+ tables on the left-hand side of the hyperedge for which we are creating a
4288+ join.
4289+
4290+ @param right_path An access path which joins together a superset of all the
4291+ tables on the right-hand side of the hyperedge for which we are creating a
4292+ join.
4293+
4294+ @param left The set of tables joined together in "left_path".
4295+
4296+ @param right The set of tables joined together in "right_path".
4297+
4298+ @param left_reachable The set of tables that can be joined directly with
4299+ "left_path", with no intermediate join being performed first. If a table is in
4300+ this set, it is possible to construct a nested loop join between an access
4301+ path accessing only that table and the access path pointed to by "left_path".
4302+
4303+ @param right_reachable The set of tables that can be joined directly with
4304+ "right_path", with no intermediate join being performed first. If a table is
4305+ in this set, it is possible to construct a nested loop join between an access
4306+ path accessing only that table and the access path pointed to by "right_path".
4307+
4308+ @param is_reorderable True if the optimizer may try to construct a nested loop
4309+ join between "left_path" and "right_path" in either direction. False if the
4310+ optimizer will consider nested loop joins in only one direction, with
4311+ "left_path" as the outer table and "right_path" as the inner table. When it is
4312+ true, we disallow a parameterized join path only if it is possible to resolve
4313+ more parameter tables first in both join orders. This is slightly more lenient
4314+ than it has to be, as it will allow parameterized join paths with both join
4315+ orders, even though one of the orders can join with a parameter table first.
4316+ Since all of these joins will be parameterized on the same set of tables, this
4317+ extra leniency is not believed to contribute much to the explosion of plans
4318+ with different parameterizations.
42484319 */
42494320bool DisallowParameterizedJoinPath (AccessPath *left_path,
42504321 AccessPath *right_path, NodeMap left,
42514322 NodeMap right, NodeMap left_reachable,
4252- NodeMap right_reachable) {
4323+ NodeMap right_reachable,
4324+ bool is_reorderable) {
42534325 const NodeMap left_parameters = left_path->parameter_tables & ~RAND_TABLE_BIT;
42544326 const NodeMap right_parameters =
42554327 right_path->parameter_tables & ~RAND_TABLE_BIT;
42564328
4257- if (IsSubset (left_parameters | right_parameters, left | right)) {
4258- // Not creating a parameterized path, so it's always fine.
4329+ if (!CanResolveMoreParameterTables (left, right, left_parameters,
4330+ right_parameters, left_reachable,
4331+ right_reachable)) {
4332+ // Neither left nor right can resolve parameterization that is left
4333+ // unresolved by this join by first joining in one of the parameter tables.
4334+ // E.g., we're still on the inside of an outer join, and the parameter
4335+ // tables are outside the outer join, and we still need to join together
4336+ // more tables on the inner side of the outer join before we're allowed to
4337+ // do the outer join. We have to allow creation of a parameterized join path
4338+ // if we want to use index lookups here at all.
42594339 return false ;
42604340 }
42614341
4262- if (!Overlaps (right_parameters, right_reachable) &&
4263- !Overlaps (left_parameters, left_reachable)) {
4264- // Either left or right cannot resolve any of their parameterizations yet
4265- // (e.g., we're still on the inside of an outer join that we cannot
4266- // finish yet), so we cannot avoid keeping them if we want to use index
4267- // lookups here at all.
4268- return false ;
4269- }
4270-
4271- // If the outer table partially, but not fully, resolves the inner table's
4272- // parameterization, we still allow it (otherwise, we could not have
4273- // multi-part index lookups where the keyparts come from different tables).
4274- // This is the so-called “star-schema exception”.
4275- //
4276- // We need to check both ways, in case we try to swap them for a hash join.
4277- // Only one of these will ever be true in any given join anyway (joins where
4278- // we try to resolve the outer path's parameterizations with the inner one
4279- // are disallowed), so we do not allow more than is required.
4280- if (PartiallyResolvedParameterization (left_parameters, right) ||
4281- PartiallyResolvedParameterization (right_parameters, left)) {
4342+ // If the join can be performed both ways (such as a commutable join
4343+ // operation, or a semijoin that can be rewritten to an inner join), we're a
4344+ // bit more lenient and allow creation of a parameterized join path even
4345+ // though a parameter table can be resolved first, if it is not possible to
4346+ // resolve any parameter tables first in the reordered join. Otherwise, we
4347+ // might not be able to use indexes in the reordered join.
4348+ if (is_reorderable && !CanResolveMoreParameterTables (
4349+ right, left, right_parameters, left_parameters,
4350+ right_reachable, left_reachable)) {
42824351 return false ;
42834352 }
42844353
@@ -4565,9 +4634,21 @@ bool CostingReceiver::FoundSubgraphPair(NodeMap left, NodeMap right,
45654634 zero_path->delayed_predicates = right_path->delayed_predicates ;
45664635 right_path = zero_path;
45674636 }
4637+
4638+ // Can this join be performed in both left-right and right-left order? It
4639+ // can if the join operation is commutative (or rewritable to one) and
4640+ // right_path's parameterization doesn't force it to be on the right side.
4641+ // If this condition is true, the right-left join will be attempted proposed
4642+ // in addition to the left-right join, but the additional checks in
4643+ // AllowNestedLoopJoin() and AllowHashJoin() decide if they are actually
4644+ // proposed.
4645+ const bool is_reorderable = (is_commutative || can_rewrite_semi_to_inner) &&
4646+ !Overlaps (right_path->parameter_tables , left);
4647+
45684648 for (AccessPath *left_path : left_it->second .paths ) {
45694649 if (DisallowParameterizedJoinPath (left_path, right_path, left, right,
4570- left_reachable, right_reachable)) {
4650+ left_reachable, right_reachable,
4651+ is_reorderable)) {
45714652 continue ;
45724653 }
45734654
@@ -4606,7 +4687,7 @@ bool CostingReceiver::FoundSubgraphPair(NodeMap left, NodeMap right,
46064687 new_obsolete_orderings,
46074688 /* rewrite_semi_to_inner=*/ false , &wrote_trace);
46084689 }
4609- if (is_commutative || can_rewrite_semi_to_inner ) {
4690+ if (is_reorderable ) {
46104691 ProposeHashJoin (right, left, right_path, left_path, edge, new_fd_set,
46114692 new_obsolete_orderings,
46124693 /* rewrite_semi_to_inner=*/ can_rewrite_semi_to_inner,
@@ -4617,7 +4698,7 @@ bool CostingReceiver::FoundSubgraphPair(NodeMap left, NodeMap right,
46174698 ProposeNestedLoopJoin (left, right, left_path, right_path, edge,
46184699 /* rewrite_semi_to_inner=*/ false , new_fd_set,
46194700 new_obsolete_orderings, &wrote_trace);
4620- if (is_commutative || can_rewrite_semi_to_inner ) {
4701+ if (is_reorderable ) {
46214702 ProposeNestedLoopJoin (
46224703 right, left, right_path, left_path, edge,
46234704 /* rewrite_semi_to_inner=*/ can_rewrite_semi_to_inner, new_fd_set,
0 commit comments