@@ -4228,12 +4228,49 @@ NodeMap FindReachableTablesFrom(NodeMap tables, const JoinHypergraph &graph) {
4228
4228
return reachable;
4229
4229
}
4230
4230
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 ;
4237
4274
}
4238
4275
4239
4276
/* *
@@ -4245,40 +4282,72 @@ bool PartiallyResolvedParameterization(NodeMap parameter_tables,
4245
4282
plans to be left-deep (since such plans never gain anything from being
4246
4283
bushy), reducing the search space significantly without compromising
4247
4284
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.
4248
4319
*/
4249
4320
bool DisallowParameterizedJoinPath (AccessPath *left_path,
4250
4321
AccessPath *right_path, NodeMap left,
4251
4322
NodeMap right, NodeMap left_reachable,
4252
- NodeMap right_reachable) {
4323
+ NodeMap right_reachable,
4324
+ bool is_reorderable) {
4253
4325
const NodeMap left_parameters = left_path->parameter_tables & ~RAND_TABLE_BIT;
4254
4326
const NodeMap right_parameters =
4255
4327
right_path->parameter_tables & ~RAND_TABLE_BIT;
4256
4328
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.
4259
4339
return false ;
4260
4340
}
4261
4341
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)) {
4282
4351
return false ;
4283
4352
}
4284
4353
@@ -4565,9 +4634,21 @@ bool CostingReceiver::FoundSubgraphPair(NodeMap left, NodeMap right,
4565
4634
zero_path->delayed_predicates = right_path->delayed_predicates ;
4566
4635
right_path = zero_path;
4567
4636
}
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
+
4568
4648
for (AccessPath *left_path : left_it->second .paths ) {
4569
4649
if (DisallowParameterizedJoinPath (left_path, right_path, left, right,
4570
- left_reachable, right_reachable)) {
4650
+ left_reachable, right_reachable,
4651
+ is_reorderable)) {
4571
4652
continue ;
4572
4653
}
4573
4654
@@ -4606,7 +4687,7 @@ bool CostingReceiver::FoundSubgraphPair(NodeMap left, NodeMap right,
4606
4687
new_obsolete_orderings,
4607
4688
/* rewrite_semi_to_inner=*/ false , &wrote_trace);
4608
4689
}
4609
- if (is_commutative || can_rewrite_semi_to_inner ) {
4690
+ if (is_reorderable ) {
4610
4691
ProposeHashJoin (right, left, right_path, left_path, edge, new_fd_set,
4611
4692
new_obsolete_orderings,
4612
4693
/* rewrite_semi_to_inner=*/ can_rewrite_semi_to_inner,
@@ -4617,7 +4698,7 @@ bool CostingReceiver::FoundSubgraphPair(NodeMap left, NodeMap right,
4617
4698
ProposeNestedLoopJoin (left, right, left_path, right_path, edge,
4618
4699
/* rewrite_semi_to_inner=*/ false , new_fd_set,
4619
4700
new_obsolete_orderings, &wrote_trace);
4620
- if (is_commutative || can_rewrite_semi_to_inner ) {
4701
+ if (is_reorderable ) {
4621
4702
ProposeNestedLoopJoin (
4622
4703
right, left, right_path, left_path, edge,
4623
4704
/* rewrite_semi_to_inner=*/ can_rewrite_semi_to_inner, new_fd_set,
0 commit comments