From ed83f6e38209016ae75832ae8997f190ea4a0c8e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 10 May 2010 16:25:46 +0000 Subject: [PATCH] When adding a "target IS NOT NULL" indexqual to the plan for an index-optimized MIN or MAX, we must take care to insert the added qual in a legal place among the existing indexquals, if any. The btree index AM requires the quals to appear in index-column order. We didn't have to worry about this before because "target IS NOT NULL" was just treated as a plain scan filter condition; but as of 9.0 it can be an index qual and then it has to follow the rule. Per report from Ian Barwick. --- src/backend/optimizer/plan/planagg.c | 185 ++++++++++++++++++++++++--- 1 file changed, 166 insertions(+), 19 deletions(-) diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 021662607b..104ec53e5f 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.51 2010/02/14 18:42:15 rhaas Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.52 2010/05/10 16:25:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -50,6 +50,7 @@ static bool build_minmax_path(PlannerInfo *root, RelOptInfo *rel, static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info, IndexOptInfo *index, int indexcol); static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info); +static void attach_notnull_index_qual(MinMaxAggInfo *info, IndexScan *iplan); static Node *replace_aggs_with_params_mutator(Node *node, List **context); static Oid fetch_agg_sort_op(Oid aggfnoid); @@ -537,9 +538,6 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) * The NOT NULL qual has to go on the actual indexscan; create_plan might * have stuck a gating Result atop that, if there were any pseudoconstant * quals. - * - * We can skip adding the NOT NULL qual if it duplicates either an - * already-given WHERE condition, or a clause of the index predicate. */ plan = create_plan(&subroot, (Path *) info->path); @@ -552,21 +550,7 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) if (!IsA(iplan, IndexScan)) elog(ERROR, "result of create_plan(IndexPath) isn't an IndexScan"); - if (!list_member(iplan->indexqualorig, info->notnulltest) && - !list_member(info->path->indexinfo->indpred, info->notnulltest)) - { - NullTest *ntest; - - /* Need a "fixed" copy as well as the original */ - ntest = copyObject(info->notnulltest); - ntest->arg = (Expr *) fix_indexqual_operand((Node *) ntest->arg, - info->path->indexinfo); - - iplan->indexqual = lappend(iplan->indexqual, - ntest); - iplan->indexqualorig = lappend(iplan->indexqualorig, - info->notnulltest); - } + attach_notnull_index_qual(info, iplan); plan = (Plan *) make_limit(plan, subparse->limitOffset, @@ -586,6 +570,169 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) root->init_plans = subroot.init_plans; } +/* + * Add "target IS NOT NULL" to the quals of the given indexscan. + * + * This is trickier than it sounds because the new qual has to be added at an + * appropriate place in the qual list, to preserve the list's ordering by + * index column position. + */ +static void +attach_notnull_index_qual(MinMaxAggInfo *info, IndexScan *iplan) +{ + NullTest *ntest; + List *newindexqual; + List *newindexqualorig; + bool done; + ListCell *lc1; + ListCell *lc2; + Expr *leftop; + AttrNumber targetattno; + + /* + * We can skip adding the NOT NULL qual if it duplicates either an + * already-given WHERE condition, or a clause of the index predicate. + */ + if (list_member(iplan->indexqualorig, info->notnulltest) || + list_member(info->path->indexinfo->indpred, info->notnulltest)) + return; + + /* Need a "fixed" copy as well as the original */ + ntest = copyObject(info->notnulltest); + ntest->arg = (Expr *) fix_indexqual_operand((Node *) ntest->arg, + info->path->indexinfo); + + /* Identify the target index column from the "fixed" copy */ + leftop = ntest->arg; + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!IsA(leftop, Var)) + elog(ERROR, "NullTest indexqual has wrong key"); + + targetattno = ((Var *) leftop)->varattno; + + /* + * list.c doesn't expose a primitive to insert a list cell at an arbitrary + * position, so our strategy is to copy the lists and insert the null test + * when we reach an appropriate spot. + */ + newindexqual = newindexqualorig = NIL; + done = false; + + forboth(lc1, iplan->indexqual, lc2, iplan->indexqualorig) + { + Expr *qual = (Expr *) lfirst(lc1); + Expr *qualorig = (Expr *) lfirst(lc2); + AttrNumber varattno; + + /* + * Identify which index column this qual is for. This code should + * match the qual disassembly code in ExecIndexBuildScanKeys. + */ + if (IsA(qual, OpExpr)) + { + /* indexkey op expression */ + leftop = (Expr *) get_leftop(qual); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!IsA(leftop, Var)) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + } + else if (IsA(qual, RowCompareExpr)) + { + /* (indexkey, indexkey, ...) op (expression, expression, ...) */ + RowCompareExpr *rc = (RowCompareExpr *) qual; + + /* + * Examine just the first column of the rowcompare, which is + * what determines its placement in the overall qual list. + */ + leftop = (Expr *) linitial(rc->largs); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!IsA(leftop, Var)) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + } + else if (IsA(qual, ScalarArrayOpExpr)) + { + /* indexkey op ANY (array-expression) */ + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual; + + leftop = (Expr *) linitial(saop->args); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!IsA(leftop, Var)) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + } + else if (IsA(qual, NullTest)) + { + /* indexkey IS NULL or indexkey IS NOT NULL */ + NullTest *ntest = (NullTest *) qual; + + leftop = ntest->arg; + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!IsA(leftop, Var)) + elog(ERROR, "NullTest indexqual has wrong key"); + + varattno = ((Var *) leftop)->varattno; + } + else + { + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(qual)); + varattno = 0; /* keep compiler quiet */ + } + + /* Insert the null test at the first place it can legally go */ + if (!done && targetattno <= varattno) + { + newindexqual = lappend(newindexqual, ntest); + newindexqualorig = lappend(newindexqualorig, info->notnulltest); + done = true; + } + + newindexqual = lappend(newindexqual, qual); + newindexqualorig = lappend(newindexqualorig, qualorig); + } + + /* Add the null test at the end if it must follow all existing quals */ + if (!done) + { + newindexqual = lappend(newindexqual, ntest); + newindexqualorig = lappend(newindexqualorig, info->notnulltest); + } + + iplan->indexqual = newindexqual; + iplan->indexqualorig = newindexqualorig; +} + /* * Replace original aggregate calls with subplan output Params */ -- 2.40.0