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

Commit 106264c

Browse files
committed
Teach planagg.c that partial indexes specifying WHERE foo IS NOT NULL can be
used to perform MIN(foo) or MAX(foo), since we want to discard null rows in the indexscan anyway. (This would probably fall out for free if we were injecting the IS NOT NULL clause somewhere earlier, but given the current anatomy of the MIN/MAX optimization code we have to do it explicitly. Fortunately, very little added code is needed.) Per a discussion with Henk de Wit.
1 parent 5c8eb92 commit 106264c

File tree

1 file changed

+32
-10
lines changed

1 file changed

+32
-10
lines changed

src/backend/optimizer/plan/planagg.c

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.32 2007/04/27 22:05:47 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.33 2007/10/13 00:58:03 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -22,6 +22,7 @@
2222
#include "optimizer/pathnode.h"
2323
#include "optimizer/paths.h"
2424
#include "optimizer/planmain.h"
25+
#include "optimizer/predtest.h"
2526
#include "optimizer/subselect.h"
2627
#include "parser/parse_clause.h"
2728
#include "parser/parse_expr.h"
@@ -35,6 +36,7 @@ typedef struct
3536
Oid aggfnoid; /* pg_proc Oid of the aggregate */
3637
Oid aggsortop; /* Oid of its sort operator */
3738
Expr *target; /* expression we are aggregating on */
39+
Expr *notnulltest; /* expression for "target IS NOT NULL" */
3840
IndexPath *path; /* access path for index scan */
3941
Cost pathcost; /* estimated cost to fetch first row */
4042
bool nulls_first; /* null ordering direction matching index */
@@ -285,8 +287,23 @@ build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info)
285287
IndexPath *best_path = NULL;
286288
Cost best_cost = 0;
287289
bool best_nulls_first = false;
290+
NullTest *ntest;
291+
List *allquals;
288292
ListCell *l;
289293

294+
/* Build "target IS NOT NULL" expression for use below */
295+
ntest = makeNode(NullTest);
296+
ntest->nulltesttype = IS_NOT_NULL;
297+
ntest->arg = copyObject(info->target);
298+
info->notnulltest = (Expr *) ntest;
299+
300+
/*
301+
* Build list of existing restriction clauses plus the notnull test.
302+
* We cheat a bit by not bothering with a RestrictInfo node for the
303+
* notnull test --- predicate_implied_by() won't care.
304+
*/
305+
allquals = list_concat(list_make1(ntest), rel->baserestrictinfo);
306+
290307
foreach(l, rel->indexlist)
291308
{
292309
IndexOptInfo *index = (IndexOptInfo *) lfirst(l);
@@ -302,8 +319,13 @@ build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info)
302319
if (index->relam != BTREE_AM_OID)
303320
continue;
304321

305-
/* Ignore partial indexes that do not match the query */
306-
if (index->indpred != NIL && !index->predOK)
322+
/*
323+
* Ignore partial indexes that do not match the query --- unless
324+
* their predicates can be proven from the baserestrict list plus
325+
* the IS NOT NULL test. In that case we can use them.
326+
*/
327+
if (index->indpred != NIL && !index->predOK &&
328+
!predicate_implied_by(index->indpred, allquals))
307329
continue;
308330

309331
/*
@@ -441,7 +463,6 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
441463
Plan *iplan;
442464
TargetEntry *tle;
443465
SortClause *sortcl;
444-
NullTest *ntest;
445466

446467
/*
447468
* Generate a suitably modified query. Much of the work here is probably
@@ -487,7 +508,7 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
487508
* basic indexscan, but we have to convert it to a Plan and attach a LIMIT
488509
* node above it.
489510
*
490-
* Also we must add a "WHERE foo IS NOT NULL" restriction to the
511+
* Also we must add a "WHERE target IS NOT NULL" restriction to the
491512
* indexscan, to be sure we don't return a NULL, which'd be contrary to
492513
* the standard behavior of MIN/MAX. XXX ideally this should be done
493514
* earlier, so that the selectivity of the restriction could be included
@@ -497,6 +518,9 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
497518
* The NOT NULL qual has to go on the actual indexscan; create_plan might
498519
* have stuck a gating Result atop that, if there were any pseudoconstant
499520
* quals.
521+
*
522+
* We can skip adding the NOT NULL qual if it's redundant with either
523+
* an already-given WHERE condition, or a clause of the index predicate.
500524
*/
501525
plan = create_plan(&subroot, (Path *) info->path);
502526

@@ -508,11 +532,9 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
508532
iplan = plan;
509533
Assert(IsA(iplan, IndexScan));
510534

511-
ntest = makeNode(NullTest);
512-
ntest->nulltesttype = IS_NOT_NULL;
513-
ntest->arg = copyObject(info->target);
514-
515-
iplan->qual = lcons(ntest, iplan->qual);
535+
if (!list_member(iplan->qual, info->notnulltest) &&
536+
!list_member(info->path->indexinfo->indpred, info->notnulltest))
537+
iplan->qual = lcons(info->notnulltest, iplan->qual);
516538

517539
plan = (Plan *) make_limit(plan,
518540
subparse->limitOffset,

0 commit comments

Comments
 (0)