]> granicus.if.org Git - postgresql/commitdiff
When adding a "target IS NOT NULL" indexqual to the plan for an index-optimized
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 10 May 2010 16:25:46 +0000 (16:25 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 10 May 2010 16:25:46 +0000 (16:25 +0000)
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

index 021662607ba5d8f07fd25f7618a03bdaf322e983..104ec53e5f28b5923a7bae7e6517bb86a9fa6ec7 100644 (file)
@@ -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
  */