Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 30a695d

Browse files
committed
Be smart about outer-join qualifications that mention only one side of
the join, per recent discussion on pgsql-sql. Not clear that this will come up often in real queries, but it's not any more expensive to do it right, so we may as well do it right.
1 parent a1dbe52 commit 30a695d

File tree

1 file changed

+89
-62
lines changed

1 file changed

+89
-62
lines changed

src/backend/optimizer/plan/initsplan.c

Lines changed: 89 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.84 2003/02/08 20:20:54 tgl Exp $
11+
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.85 2003/03/02 23:46:34 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -36,10 +36,10 @@
3636
static void mark_baserels_for_outer_join(Query *root, Relids rels,
3737
Relids outerrels);
3838
static void distribute_qual_to_rels(Query *root, Node *clause,
39-
bool ispusheddown,
40-
bool isouterjoin,
41-
bool isdeduced,
42-
Relids qualscope);
39+
bool ispusheddown,
40+
bool isdeduced,
41+
Relids outerjoin_nonnullable,
42+
Relids qualscope);
4343
static void add_vars_to_targetlist(Query *root, List *vars);
4444
static bool qual_is_redundant(Query *root, RestrictInfo *restrictinfo,
4545
List *restrictlist);
@@ -209,48 +209,53 @@ distribute_quals_to_rels(Query *root, Node *jtnode)
209209
*/
210210
foreach(qual, (List *) f->quals)
211211
distribute_qual_to_rels(root, (Node *) lfirst(qual),
212-
true, false, false, result);
212+
true, false, NULL, result);
213213
}
214214
else if (IsA(jtnode, JoinExpr))
215215
{
216216
JoinExpr *j = (JoinExpr *) jtnode;
217217
Relids leftids,
218-
rightids;
219-
bool isouterjoin;
218+
rightids,
219+
nonnullable_rels,
220+
nullable_rels;
220221
List *qual;
221222

222223
/*
223224
* Order of operations here is subtle and critical. First we
224225
* recurse to handle sub-JOINs. Their join quals will be placed
225226
* without regard for whether this level is an outer join, which
226-
* is correct. Then, if we are an outer join, we mark baserels
227-
* contained within the nullable side(s) with our own rel set;
228-
* this will restrict placement of subsequent quals using those
229-
* rels, including our own quals and quals above us in the join
230-
* tree. Finally we place our own join quals.
227+
* is correct. Then we place our own join quals, which are restricted
228+
* by lower outer joins in any case, and are forced to this level if
229+
* this is an outer join and they mention the outer side. Finally, if
230+
* this is an outer join, we mark baserels contained within the inner
231+
* side(s) with our own rel set; this will prevent quals above us in
232+
* the join tree that use those rels from being pushed down below this
233+
* level. (It's okay for upper quals to be pushed down to the outer
234+
* side, however.)
231235
*/
232236
leftids = distribute_quals_to_rels(root, j->larg);
233237
rightids = distribute_quals_to_rels(root, j->rarg);
234238

235239
result = bms_union(leftids, rightids);
236240

237-
isouterjoin = false;
241+
nonnullable_rels = nullable_rels = NULL;
238242
switch (j->jointype)
239243
{
240244
case JOIN_INNER:
241245
/* Inner join adds no restrictions for quals */
242246
break;
243247
case JOIN_LEFT:
244-
mark_baserels_for_outer_join(root, rightids, result);
245-
isouterjoin = true;
248+
nonnullable_rels = leftids;
249+
nullable_rels = rightids;
246250
break;
247251
case JOIN_FULL:
248-
mark_baserels_for_outer_join(root, result, result);
249-
isouterjoin = true;
252+
/* each side is both outer and inner */
253+
nonnullable_rels = result;
254+
nullable_rels = result;
250255
break;
251256
case JOIN_RIGHT:
252-
mark_baserels_for_outer_join(root, leftids, result);
253-
isouterjoin = true;
257+
nonnullable_rels = rightids;
258+
nullable_rels = leftids;
254259
break;
255260
case JOIN_UNION:
256261

@@ -269,7 +274,11 @@ distribute_quals_to_rels(Query *root, Node *jtnode)
269274

270275
foreach(qual, (List *) j->quals)
271276
distribute_qual_to_rels(root, (Node *) lfirst(qual),
272-
false, isouterjoin, false, result);
277+
false, false,
278+
nonnullable_rels, result);
279+
280+
if (nullable_rels != NULL)
281+
mark_baserels_for_outer_join(root, nullable_rels, result);
273282
}
274283
else
275284
elog(ERROR, "distribute_quals_to_rels: unexpected node type %d",
@@ -324,14 +333,16 @@ mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
324333
* (depending on whether the clause is a join) of each base relation
325334
* mentioned in the clause. A RestrictInfo node is created and added to
326335
* the appropriate list for each rel. Also, if the clause uses a
327-
* mergejoinable operator and is not an outer-join qual, enter the left-
328-
* and right-side expressions into the query's lists of equijoined vars.
336+
* mergejoinable operator and is not delayed by outer-join rules, enter
337+
* the left- and right-side expressions into the query's lists of
338+
* equijoined vars.
329339
*
330340
* 'clause': the qual clause to be distributed
331341
* 'ispusheddown': if TRUE, force the clause to be marked 'ispusheddown'
332342
* (this indicates the clause came from a FromExpr, not a JoinExpr)
333-
* 'isouterjoin': TRUE if the qual came from an OUTER JOIN's ON-clause
334343
* 'isdeduced': TRUE if the qual came from implied-equality deduction
344+
* 'outerjoin_nonnullable': NULL if not an outer-join qual, else the set of
345+
* baserels appearing on the outer (nonnullable) side of the join
335346
* 'qualscope': set of baserels the qual's syntactic scope covers
336347
*
337348
* 'qualscope' identifies what level of JOIN the qual came from. For a top
@@ -341,8 +352,8 @@ mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
341352
static void
342353
distribute_qual_to_rels(Query *root, Node *clause,
343354
bool ispusheddown,
344-
bool isouterjoin,
345355
bool isdeduced,
356+
Relids outerjoin_nonnullable,
346357
Relids qualscope)
347358
{
348359
RestrictInfo *restrictinfo = makeNode(RestrictInfo);
@@ -392,63 +403,80 @@ distribute_qual_to_rels(Query *root, Node *clause,
392403
relids = qualscope;
393404

394405
/*
395-
* For an outer-join qual, pretend that the clause references all rels
396-
* appearing within its syntactic scope, even if it really doesn't.
397-
* This ensures that the clause will be evaluated exactly at the level
398-
* of joining corresponding to the outer join.
399-
*
400-
* For a non-outer-join qual, we can evaluate the qual as soon as (1) we
401-
* have all the rels it mentions, and (2) we are at or above any outer
402-
* joins that can null any of these rels and are below the syntactic
403-
* location of the given qual. To enforce the latter, scan the base
404-
* rels listed in relids, and merge their outer-join sets into the
405-
* clause's own reference list. At the time we are called, the
406-
* outerjoinset of each baserel will show exactly those outer
407-
* joins that are below the qual in the join tree.
408-
*
409-
* If the qual came from implied-equality deduction, we can evaluate the
410-
* qual at its natural semantic level.
411-
*
406+
* Check to see if clause application must be delayed by outer-join
407+
* considerations.
412408
*/
413409
if (isdeduced)
414410
{
411+
/*
412+
* If the qual came from implied-equality deduction, we can evaluate
413+
* the qual at its natural semantic level. It is not affected by
414+
* any outer-join rules (else we'd not have decided the vars were
415+
* equal).
416+
*/
415417
Assert(bms_equal(relids, qualscope));
416418
can_be_equijoin = true;
417419
}
418-
else if (isouterjoin)
420+
else if (bms_overlap(relids, outerjoin_nonnullable))
419421
{
422+
/*
423+
* The qual is attached to an outer join and mentions (some of the)
424+
* rels on the nonnullable side. Force the qual to be evaluated
425+
* exactly at the level of joining corresponding to the outer join.
426+
* We cannot let it get pushed down into the nonnullable side, since
427+
* then we'd produce no output rows, rather than the intended single
428+
* null-extended row, for any nonnullable-side rows failing the qual.
429+
*
430+
* Note: an outer-join qual that mentions only nullable-side rels can
431+
* be pushed down into the nullable side without changing the join
432+
* result, so we treat it the same as an ordinary inner-join qual.
433+
*/
420434
relids = qualscope;
421435
can_be_equijoin = false;
422436
}
423437
else
424438
{
425-
/* copy to ensure we don't change caller's qualscope set */
426-
Relids newrelids = bms_copy(relids);
439+
/*
440+
* For a non-outer-join qual, we can evaluate the qual as soon as
441+
* (1) we have all the rels it mentions, and (2) we are at or above
442+
* any outer joins that can null any of these rels and are below the
443+
* syntactic location of the given qual. To enforce the latter, scan
444+
* the base rels listed in relids, and merge their outer-join sets
445+
* into the clause's own reference list. At the time we are called,
446+
* the outerjoinset of each baserel will show exactly those outer
447+
* joins that are below the qual in the join tree.
448+
*/
449+
Relids addrelids = NULL;
427450
Relids tmprelids;
428451
int relno;
429452

430-
can_be_equijoin = true;
431453
tmprelids = bms_copy(relids);
432454
while ((relno = bms_first_member(tmprelids)) >= 0)
433455
{
434456
RelOptInfo *rel = find_base_rel(root, relno);
435457

436-
if (!bms_is_subset(rel->outerjoinset, relids))
437-
{
438-
newrelids = bms_add_members(newrelids, rel->outerjoinset);
439-
440-
/*
441-
* Because application of the qual will be delayed by
442-
* outer join, we mustn't assume its vars are equal
443-
* everywhere.
444-
*/
445-
can_be_equijoin = false;
446-
}
458+
if (rel->outerjoinset != NULL)
459+
addrelids = bms_add_members(addrelids, rel->outerjoinset);
447460
}
448461
bms_free(tmprelids);
449-
relids = newrelids;
450-
/* Should still be a subset of current scope ... */
451-
Assert(bms_is_subset(relids, qualscope));
462+
463+
if (bms_is_subset(addrelids, relids))
464+
{
465+
/* Qual is not affected by any outer-join restriction */
466+
can_be_equijoin = true;
467+
}
468+
else
469+
{
470+
relids = bms_union(relids, addrelids);
471+
/* Should still be a subset of current scope ... */
472+
Assert(bms_is_subset(relids, qualscope));
473+
/*
474+
* Because application of the qual will be delayed by outer join,
475+
* we mustn't assume its vars are equal everywhere.
476+
*/
477+
can_be_equijoin = false;
478+
}
479+
bms_free(addrelids);
452480
}
453481

454482
/*
@@ -725,8 +753,7 @@ process_implied_equality(Query *root,
725753
* taken for an original JOIN/ON clause.
726754
*/
727755
distribute_qual_to_rels(root, (Node *) clause,
728-
true, false, true,
729-
relids);
756+
true, true, NULL, relids);
730757
}
731758

732759
/*

0 commit comments

Comments
 (0)