]> granicus.if.org Git - postgresql/commitdiff
Teach planner to optionally ignore index columns that have an equality
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 14 Jun 2005 04:04:30 +0000 (04:04 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 14 Jun 2005 04:04:30 +0000 (04:04 +0000)
constraint while determining whether the index sort order matches the
query's ORDER BY.  This for example allows an index on (x,y) to match
... WHERE x = 42 ORDER BY y;
It only works for btree indexes, but since those are the only ones we
currently have that are ordered at all, that's good enough for now.
Per popular demand.

src/backend/optimizer/path/indxpath.c

index 525304f5cb313e76a7a81a5721f03f4a4c8bb049..be684094119c5b7f7588502edbb1ab86c187e07a 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.184 2005/06/13 23:14:48 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.185 2005/06/14 04:04:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -65,6 +65,17 @@ static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel,
                                                          Relids outer_relids);
 static List *find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel,
                                                                   Relids outer_relids, bool isouterjoin);
+static bool match_variant_ordering(PlannerInfo *root,
+                                                                  IndexOptInfo *index,
+                                                                  List *restrictclauses,
+                                                                  ScanDirection *indexscandir);
+static List *identify_ignorable_ordering_cols(PlannerInfo *root,
+                                                                                         IndexOptInfo *index,
+                                                                                         List *restrictclauses);
+static bool match_index_to_query_keys(PlannerInfo *root,
+                                                                         IndexOptInfo *index,
+                                                                         ScanDirection indexscandir,
+                                                                         List *ignorables);
 static bool match_boolean_index_clause(Node *clause, int indexcol,
                                                                           IndexOptInfo *index);
 static bool match_special_index_operator(Expr *clause, Oid opclass,
@@ -315,22 +326,24 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
                }
 
                /*
-                * 4. If the index is ordered, a backwards scan might be
-                * interesting. Currently this is only possible for a DESC query
-                * result ordering.
+                * 4. If the index is ordered, and there is a requested query
+                * ordering that we failed to match, consider variant ways of
+                * achieving the ordering.  Again, this is only interesting
+                * at top level.
                 */
-               if (istoplevel && index_is_ordered && !isjoininner)
+               if (istoplevel && index_is_ordered && !isjoininner &&
+                       root->query_pathkeys != NIL &&
+                       pathkeys_useful_for_ordering(root, useful_pathkeys) == 0)
                {
-                       index_pathkeys = build_index_pathkeys(root, index,
-                                                                                                 BackwardScanDirection);
-                       useful_pathkeys = truncate_useless_pathkeys(root, rel,
-                                                                                                               index_pathkeys);
-                       if (useful_pathkeys != NIL)
+                       ScanDirection   indexscandir;
+
+                       if (match_variant_ordering(root, index, restrictclauses,
+                                                                          &indexscandir))
                        {
                                ipath = create_index_path(root, index,
                                                                                  restrictclauses,
-                                                                                 useful_pathkeys,
-                                                                                 BackwardScanDirection,
+                                                                                 root->query_pathkeys,
+                                                                                 indexscandir,
                                                                                  false);
                                result = lappend(result, ipath);
                        }
@@ -1220,6 +1233,255 @@ find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel,
        return clause_list;
 }
 
+/****************************************************************************
+ *                             ----  ROUTINES TO HANDLE PATHKEYS  ----
+ ****************************************************************************/
+
+/*
+ * match_variant_ordering
+ *             Try to match an index's ordering to the query's requested ordering
+ *
+ * This is used when the index is ordered but a naive comparison fails to
+ * match its ordering (pathkeys) to root->query_pathkeys.  It may be that
+ * we need to scan the index backwards.  Also, a less naive comparison can
+ * help for both forward and backward indexscans.  Columns of the index
+ * that have an equality restriction clause can be ignored in the match;
+ * that is, an index on (x,y) can be considered to match the ordering of
+ *             ... WHERE x = 42 ORDER BY y;
+ *
+ * Note: it would be possible to similarly ignore useless ORDER BY items;
+ * that is, an index on just y could be considered to match the ordering of
+ *             ... WHERE x = 42 ORDER BY x, y;
+ * But proving that this is safe would require finding a btree opclass
+ * containing both the = operator and the < or > operator in the ORDER BY
+ * item.  That's significantly more expensive than what we do here, since
+ * we'd have to look at restriction clauses unrelated to the current index
+ * and search for opclasses without any hint from the index.  The practical
+ * use-cases seem to be mostly covered by ignoring index columns, so that's
+ * all we do for now.
+ *
+ * Inputs:
+ * 'index' is the index of interest.
+ * 'restrictclauses' is the list of sublists of restriction clauses
+ *             matching the columns of the index (NIL if none)
+ *
+ * Returns TRUE if able to match the requested query pathkeys, FALSE if not.
+ * In the TRUE case, sets '*indexscandir' to either ForwardScanDirection or
+ * BackwardScanDirection to indicate the proper scan direction.
+ */
+static bool
+match_variant_ordering(PlannerInfo *root,
+                                          IndexOptInfo *index,
+                                          List *restrictclauses,
+                                          ScanDirection *indexscandir)
+{
+       List       *ignorables;
+
+       /*
+        * Forget the whole thing if not a btree index; our check for ignorable
+        * columns assumes we are dealing with btree opclasses.  (It'd be possible
+        * to factor out just the try for backwards indexscan, but considering
+        * that we presently have no orderable indexes except btrees anyway,
+        * it's hardly worth contorting this code for that case.)
+        *
+        * Note: if you remove this, you probably need to put in a check on
+        * amoptionalkey to prevent possible clauseless scan on an index that
+        * won't cope.
+        */
+       if (index->relam != BTREE_AM_OID)
+               return false;
+       /*
+        * Figure out which index columns can be optionally ignored because
+        * they have an equality constraint.  This is the same set for either
+        * forward or backward scan, so we do it just once.
+        */
+       ignorables = identify_ignorable_ordering_cols(root, index,
+                                                                                                 restrictclauses);
+       /*
+        * Try to match to forward scan, then backward scan.  However, we can
+        * skip the forward-scan case if there are no ignorable columns,
+        * because find_usable_indexes() would have found the match already.
+        */
+       if (ignorables &&
+               match_index_to_query_keys(root, index, ForwardScanDirection,
+                                                                 ignorables))
+       {
+               *indexscandir = ForwardScanDirection;
+               return true;
+       }
+       if (match_index_to_query_keys(root, index, BackwardScanDirection,
+                                                                 ignorables))
+       {
+               *indexscandir = BackwardScanDirection;
+               return true;
+       }
+       return false;
+}
+
+/*
+ * identify_ignorable_ordering_cols
+ *             Determine which index columns can be ignored for ordering purposes
+ *
+ * Returns an integer List of column numbers (1-based) of ignorable
+ * columns.  The ignorable columns are those that have equality constraints
+ * against pseudoconstants.
+ */
+static List *
+identify_ignorable_ordering_cols(PlannerInfo *root,
+                                                                IndexOptInfo *index,
+                                                                List *restrictclauses)
+{
+       List       *result = NIL;
+       int                     indexcol = 0;                   /* note this is 0-based */
+       ListCell   *l;
+
+       /* restrictclauses is either NIL or has a sublist per column */
+       foreach(l, restrictclauses)
+       {
+               List   *sublist = (List *) lfirst(l);
+               Oid             opclass = index->classlist[indexcol];
+               ListCell *l2;
+
+               foreach(l2, sublist)
+               {
+                       RestrictInfo *rinfo = (RestrictInfo *) lfirst(l2);
+                       OpExpr     *clause = (OpExpr *) rinfo->clause;
+                       Oid             clause_op;
+                       int             op_strategy;
+                       bool    varonleft;
+                       bool    ispc;
+
+                       /* We know this clause passed match_clause_to_indexcol */
+
+                       /* First check for boolean-index cases. */
+                       if (IsBooleanOpclass(opclass))
+                       {
+                               if (match_boolean_index_clause((Node *) clause, indexcol,
+                                                                                          index))
+                               {
+                                       /*
+                                        * The clause means either col = TRUE or col = FALSE;
+                                        * we do not care which, it's an equality constraint
+                                        * either way.
+                                        */
+                                       result = lappend_int(result, indexcol+1);
+                                       break;
+                               }
+                       }
+
+                       /* Else clause must be a binary opclause. */
+                       Assert(IsA(clause, OpExpr));
+
+                       /* Determine left/right sides and check the operator */
+                       clause_op = clause->opno;
+                       if (match_index_to_operand(linitial(clause->args), indexcol,
+                                                                          index))
+                       {
+                               /* clause_op is correct */
+                               varonleft = true;
+                       }
+                       else
+                       {
+                               Assert(match_index_to_operand(lsecond(clause->args), indexcol,
+                                                                                         index));
+                               /* Must flip operator to get the opclass member */
+                               clause_op = get_commutator(clause_op);
+                               varonleft = false;
+                       }
+                       if (!OidIsValid(clause_op))
+                               continue;               /* ignore non match, per next comment */
+                       op_strategy = get_op_opclass_strategy(clause_op, opclass);
+
+                       /*
+                        * You might expect to see Assert(op_strategy != 0) here,
+                        * but you won't: the clause might contain a special indexable
+                        * operator rather than an ordinary opclass member.  Currently
+                        * none of the special operators are very likely to expand to
+                        * an equality operator; we do not bother to check, but just
+                        * assume no match.
+                        */
+                       if (op_strategy != BTEqualStrategyNumber)
+                               continue;
+
+                       /* Now check that other side is pseudoconstant */
+                       if (varonleft)
+                               ispc = is_pseudo_constant_clause_relids(lsecond(clause->args),
+                                                                                                               rinfo->right_relids);
+                       else
+                               ispc = is_pseudo_constant_clause_relids(linitial(clause->args),
+                                                                                                               rinfo->left_relids);
+                       if (ispc)
+                       {
+                               result = lappend_int(result, indexcol+1);
+                               break;
+                       }
+               }
+               indexcol++;
+       }
+       return result;
+}
+
+/*
+ * match_index_to_query_keys
+ *             Check a single scan direction for "intelligent" match to query keys
+ *
+ * 'index' is the index of interest.
+ * 'indexscandir' is the scan direction to consider
+ * 'ignorables' is an integer list of indexes of ignorable index columns
+ *
+ * Returns TRUE on successful match (ie, the query_pathkeys can be considered
+ * to match this index).
+ */
+static bool
+match_index_to_query_keys(PlannerInfo *root,
+                                                 IndexOptInfo *index,
+                                                 ScanDirection indexscandir,
+                                                 List *ignorables)
+{
+       List       *index_pathkeys;
+       ListCell   *index_cell;
+       int                     index_col;
+       ListCell   *r;
+
+       /* Get the pathkeys that exactly describe the index */
+       index_pathkeys = build_index_pathkeys(root, index, indexscandir);
+
+       /*
+        * Can we match to the query's requested pathkeys?  The inner loop
+        * skips over ignorable index columns while trying to match.
+        */
+       index_cell = list_head(index_pathkeys);
+       index_col = 0;
+
+       foreach(r, root->query_pathkeys)
+       {
+               List       *rsubkey = (List *) lfirst(r);
+
+               for (;;)
+               {
+                       List   *isubkey;
+
+                       if (index_cell == NULL)
+                               return false;
+                       isubkey = (List *) lfirst(index_cell);
+                       index_cell = lnext(index_cell);
+                       index_col++;            /* index_col is now 1-based */
+                       /*
+                        * Since we are dealing with canonicalized pathkeys, pointer
+                        * comparison is sufficient to determine a match.
+                        */
+                       if (rsubkey == isubkey)
+                               break;                  /* matched current query pathkey */
+
+                       if (!list_member_int(ignorables, index_col))
+                               return false;   /* definite failure to match */
+                       /* otherwise loop around and try to match to next index col */
+               }
+       }
+
+       return true;
+}
+
 /****************************************************************************
  *                             ----  PATH CREATION UTILITIES  ----
  ****************************************************************************/
@@ -1230,7 +1492,8 @@ find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel,
  *       of RestrictInfos.
  *
  * This is used to flatten out the result of group_clauses_by_indexkey()
- * to produce an indexclauses list.
+ * to produce an indexclauses list.  The original list structure mustn't
+ * be altered, but it's OK to share copies of the underlying RestrictInfos.
  */
 List *
 flatten_clausegroups_list(List *clausegroups)