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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy