Teach planner to optionally ignore index columns that have an equality

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.
This commit is contained in:
Tom Lane 2005-06-14 04:04:30 +00:00
parent 189f89cb0e
commit bd6bf50b03
1 changed files with 276 additions and 13 deletions

View 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)