Commit e2c2c2e8 authored by Tom Lane's avatar Tom Lane

Improve planner's handling of duplicated index column expressions.

It's potentially useful for an index to repeat the same indexable column
or expression in multiple index columns, if the columns have different
opclasses.  (If they share opclasses too, the duplicate column is pretty
useless, but nonetheless we've allowed such cases since 9.0.)  However,
the planner failed to cope with this, because createplan.c was relying on
simple equal() matching to figure out which index column each index qual
is intended for.  We do have that information available upstream in
indxpath.c, though, so the fix is to not flatten the multi-level indexquals
list when putting it into an IndexPath.  Then we can rely on the sublist
structure to identify target index columns in createplan.c.  There's a
similar issue for index ORDER BYs (the KNNGIST feature), so introduce a
multi-level-list representation for that too.  This adds a bit more
representational overhead, but we might more or less buy that back by not
having to search for matching index columns anymore in createplan.c;
likewise btcostestimate saves some cycles.

Per bug #6351 from Christian Rudolph.  Likely symptoms include the "btree
index keys must be ordered by attribute" failure shown there, as well as
"operator MMMM is not a member of opfamily NNNN".

Although this is a pre-existing problem that can be demonstrated in 9.0 and
9.1, I'm not going to back-patch it, because the API changes in the planner
seem likely to break things such as index plugins.  The corner cases where
this matters seem too narrow to justify possibly breaking things in a minor
release.
parent d5448c7d
...@@ -209,8 +209,10 @@ cost_seqscan(Path *path, PlannerInfo *root, ...@@ -209,8 +209,10 @@ cost_seqscan(Path *path, PlannerInfo *root,
* Determines and returns the cost of scanning a relation using an index. * Determines and returns the cost of scanning a relation using an index.
* *
* 'index' is the index to be used * 'index' is the index to be used
* 'indexQuals' is the list of applicable qual clauses (implicit AND semantics) * 'indexQuals' is a list of lists of applicable qual clauses (implicit AND
* 'indexOrderBys' is the list of ORDER BY operators for amcanorderbyop indexes * semantics, one sub-list per index column)
* 'indexOrderBys' is a list of lists of lists of ORDER BY expressions for
* amcanorderbyop indexes (lists per pathkey and index column)
* 'indexonly' is true if it's an index-only scan * 'indexonly' is true if it's an index-only scan
* 'outer_rel' is the outer relation when we are considering using the index * 'outer_rel' is the outer relation when we are considering using the index
* scan as the inside of a nestloop join (hence, some of the indexQuals * scan as the inside of a nestloop join (hence, some of the indexQuals
...@@ -221,8 +223,8 @@ cost_seqscan(Path *path, PlannerInfo *root, ...@@ -221,8 +223,8 @@ cost_seqscan(Path *path, PlannerInfo *root,
* additional fields of the IndexPath besides startup_cost and total_cost. * additional fields of the IndexPath besides startup_cost and total_cost.
* These fields are needed if the IndexPath is used in a BitmapIndexScan. * These fields are needed if the IndexPath is used in a BitmapIndexScan.
* *
* indexQuals is a list of RestrictInfo nodes, but indexOrderBys is a list of * indexQuals is a list of lists of RestrictInfo nodes, but indexOrderBys
* bare expressions. * is a list of lists of lists of bare expressions.
* *
* NOTE: 'indexQuals' must contain only clauses usable as index restrictions. * NOTE: 'indexQuals' must contain only clauses usable as index restrictions.
* Any additional quals evaluated as qpquals may reduce the number of returned * Any additional quals evaluated as qpquals may reduce the number of returned
......
...@@ -1148,7 +1148,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) ...@@ -1148,7 +1148,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
* Returns a list of sublists of RestrictInfo nodes for clauses that can be * Returns a list of sublists of RestrictInfo nodes for clauses that can be
* used with this index. Each sublist contains clauses that can be used * used with this index. Each sublist contains clauses that can be used
* with one index key (in no particular order); the top list is ordered by * with one index key (in no particular order); the top list is ordered by
* index key. (This is depended on by expand_indexqual_conditions().) * index key. (This is depended on by expand_indexqual_conditions() and
* fix_indexqual_references().)
* *
* We can use clauses from either the current clauses or outer_clauses lists, * We can use clauses from either the current clauses or outer_clauses lists,
* but *found_clause is set TRUE only if we used at least one clause from * but *found_clause is set TRUE only if we used at least one clause from
...@@ -1171,8 +1172,11 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) ...@@ -1171,8 +1172,11 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
* column C, and no clauses use column B. * column C, and no clauses use column B.
* *
* Note: in some circumstances we may find the same RestrictInfos coming * Note: in some circumstances we may find the same RestrictInfos coming
* from multiple places. Defend against redundant outputs by using * from multiple places. Defend against redundant outputs by keeping a side
* list_append_unique_ptr (pointer equality should be good enough). * list of already-used clauses (pointer equality should be a good enough
* check for this). That also keeps us from matching the same clause to
* multiple columns of a badly-defined index, which is unlikely to be helpful
* and is likely to give us an inflated idea of the index's selectivity.
*/ */
static List * static List *
group_clauses_by_indexkey(IndexOptInfo *index, group_clauses_by_indexkey(IndexOptInfo *index,
...@@ -1182,6 +1186,7 @@ group_clauses_by_indexkey(IndexOptInfo *index, ...@@ -1182,6 +1186,7 @@ group_clauses_by_indexkey(IndexOptInfo *index,
bool *found_clause) bool *found_clause)
{ {
List *clausegroup_list = NIL; List *clausegroup_list = NIL;
List *used_clauses = NIL;
bool found_outer_clause = false; bool found_outer_clause = false;
int indexcol; int indexcol;
...@@ -1201,13 +1206,16 @@ group_clauses_by_indexkey(IndexOptInfo *index, ...@@ -1201,13 +1206,16 @@ group_clauses_by_indexkey(IndexOptInfo *index,
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Assert(IsA(rinfo, RestrictInfo)); Assert(IsA(rinfo, RestrictInfo));
if (list_member_ptr(used_clauses, rinfo))
continue;
if (match_clause_to_indexcol(index, if (match_clause_to_indexcol(index,
indexcol, indexcol,
rinfo, rinfo,
outer_relids, outer_relids,
saop_control)) saop_control))
{ {
clausegroup = list_append_unique_ptr(clausegroup, rinfo); clausegroup = lappend(clausegroup, rinfo);
used_clauses = lappend(used_clauses, rinfo);
if (saop_control != SAOP_REQUIRE || if (saop_control != SAOP_REQUIRE ||
IsA(rinfo->clause, ScalarArrayOpExpr)) IsA(rinfo->clause, ScalarArrayOpExpr))
*found_clause = true; *found_clause = true;
...@@ -1220,13 +1228,16 @@ group_clauses_by_indexkey(IndexOptInfo *index, ...@@ -1220,13 +1228,16 @@ group_clauses_by_indexkey(IndexOptInfo *index,
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Assert(IsA(rinfo, RestrictInfo)); Assert(IsA(rinfo, RestrictInfo));
if (list_member_ptr(used_clauses, rinfo))
continue;
if (match_clause_to_indexcol(index, if (match_clause_to_indexcol(index,
indexcol, indexcol,
rinfo, rinfo,
outer_relids, outer_relids,
saop_control)) saop_control))
{ {
clausegroup = list_append_unique_ptr(clausegroup, rinfo); clausegroup = lappend(clausegroup, rinfo);
used_clauses = lappend(used_clauses, rinfo);
found_outer_clause = true; found_outer_clause = true;
} }
} }
...@@ -1240,6 +1251,8 @@ group_clauses_by_indexkey(IndexOptInfo *index, ...@@ -1240,6 +1251,8 @@ group_clauses_by_indexkey(IndexOptInfo *index,
clausegroup_list = lappend(clausegroup_list, clausegroup); clausegroup_list = lappend(clausegroup_list, clausegroup);
} }
list_free(used_clauses);
if (!*found_clause && !found_outer_clause) if (!*found_clause && !found_outer_clause)
return NIL; /* no indexable clauses anywhere */ return NIL; /* no indexable clauses anywhere */
...@@ -1293,7 +1306,7 @@ group_clauses_by_indexkey(IndexOptInfo *index, ...@@ -1293,7 +1306,7 @@ group_clauses_by_indexkey(IndexOptInfo *index,
* target index column. This is sufficient to guarantee that some index * target index column. This is sufficient to guarantee that some index
* condition can be constructed from the RowCompareExpr --- whether the * condition can be constructed from the RowCompareExpr --- whether the
* remaining columns match the index too is considered in * remaining columns match the index too is considered in
* expand_indexqual_rowcompare(). * adjust_rowcompare_for_index().
* *
* It is also possible to match ScalarArrayOpExpr clauses to indexes, when * It is also possible to match ScalarArrayOpExpr clauses to indexes, when
* the clause is of the form "indexkey op ANY (arrayconst)". Since not * the clause is of the form "indexkey op ANY (arrayconst)". Since not
...@@ -1553,13 +1566,15 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, ...@@ -1553,13 +1566,15 @@ match_rowcompare_to_indexcol(IndexOptInfo *index,
* Test whether an index can produce output ordered according to the * Test whether an index can produce output ordered according to the
* given pathkeys using "ordering operators". * given pathkeys using "ordering operators".
* *
* If it can, return a list of suitable ORDER BY expressions, each of the form * If it can, return a list of lists of lists of ORDER BY expressions,
* "indexedcol operator pseudoconstant". If not, return NIL. * each of the form "indexedcol operator pseudoconstant". If not, return NIL.
* (The top list corresponds to pathkeys and the sub-lists to index columns;
* see comments for indexorderbys in nodes/relation.h.)
*/ */
static List * static List *
match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys) match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
{ {
List *orderbyexprs = NIL; List *orderbylists = NIL;
ListCell *lc1; ListCell *lc1;
/* Only indexes with the amcanorderbyop property are interesting here */ /* Only indexes with the amcanorderbyop property are interesting here */
...@@ -1606,7 +1621,17 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys) ...@@ -1606,7 +1621,17 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
pathkey->pk_opfamily); pathkey->pk_opfamily);
if (expr) if (expr)
{ {
orderbyexprs = lappend(orderbyexprs, expr); /*
* Generate list-of-sublists representation to show which
* index column this expression matches.
*/
List *sublist = NIL;
int i;
for (i = 0; i < indexcol; i++)
sublist = lappend(sublist, NIL);
sublist = lappend(sublist, list_make1(expr));
orderbylists = lappend(orderbylists, sublist);
found = true; found = true;
break; break;
} }
...@@ -1620,7 +1645,7 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys) ...@@ -1620,7 +1645,7 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
return NIL; return NIL;
} }
return orderbyexprs; /* success! */ return orderbylists; /* success! */
} }
/* /*
...@@ -2434,9 +2459,11 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, ...@@ -2434,9 +2459,11 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
* Given a list of lists of RestrictInfos, flatten it to a list * Given a list of lists of RestrictInfos, flatten it to a list
* of RestrictInfos. * of RestrictInfos.
* *
* This is used to flatten out the result of group_clauses_by_indexkey() * This is used to flatten out a list of sublists of index clauses (such as
* to produce an indexclauses list. The original list structure mustn't * the result of group_clauses_by_indexkey()) into a single list, for use
* be altered, but it's OK to share copies of the underlying RestrictInfos. * where we don't care which clause goes with which index column. The input
* list structure mustn't be altered, but it's OK to share copies of the
* underlying RestrictInfos.
*/ */
List * List *
flatten_clausegroups_list(List *clausegroups) flatten_clausegroups_list(List *clausegroups)
...@@ -2449,6 +2476,38 @@ flatten_clausegroups_list(List *clausegroups) ...@@ -2449,6 +2476,38 @@ flatten_clausegroups_list(List *clausegroups)
return allclauses; return allclauses;
} }
/*
* flatten_indexorderbys_list
* Given a list of lists of lists of ORDER BY expressions, flatten it.
*
* This is similar to flatten_clausegroups_list, but is used to flatten the
* three-list-level result of match_index_to_pathkeys(). We assume the
* bottom lists each have zero or one member.
*/
List *
flatten_indexorderbys_list(List *indexorderbys)
{
List *allclauses = NIL;
ListCell *lc1;
foreach(lc1, indexorderbys)
{
List *sublist = (List *) lfirst(lc1);
ListCell *lc2;
foreach(lc2, sublist)
{
List *subsublist = (List *) lfirst(lc2);
if (subsublist == NIL)
continue;
Assert(list_length(subsublist) == 1);
allclauses = lappend(allclauses, (Expr *) linitial(subsublist));
}
}
return allclauses;
}
/**************************************************************************** /****************************************************************************
* ---- ROUTINES TO CHECK OPERANDS ---- * ---- ROUTINES TO CHECK OPERANDS ----
...@@ -2577,8 +2636,8 @@ match_index_to_operand(Node *operand, ...@@ -2577,8 +2636,8 @@ match_index_to_operand(Node *operand,
* converted into boolean equality operators. * converted into boolean equality operators.
* *
* expand_indexqual_conditions() converts a list of lists of RestrictInfo * expand_indexqual_conditions() converts a list of lists of RestrictInfo
* nodes (with implicit AND semantics across list elements) into * nodes (with implicit AND semantics across list elements) into a list of
* a list of clauses that the executor can actually handle. For operators * lists of clauses that the executor can actually handle. For operators
* that are members of the index's opfamily this transformation is a no-op, * that are members of the index's opfamily this transformation is a no-op,
* but clauses recognized by match_special_index_operator() or * but clauses recognized by match_special_index_operator() or
* match_boolean_index_clause() must be converted into one or more "regular" * match_boolean_index_clause() must be converted into one or more "regular"
...@@ -2796,22 +2855,20 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation, ...@@ -2796,22 +2855,20 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation,
/* /*
* expand_indexqual_conditions * expand_indexqual_conditions
* Given a list of sublists of RestrictInfo nodes, produce a flat list * Given a list of sublists of RestrictInfo nodes, produce a list of lists
* of index qual clauses. Standard qual clauses (those in the index's * of index qual clauses. Standard qual clauses (those in the index's
* opfamily) are passed through unchanged. Boolean clauses and "special" * opfamily) are passed through unchanged. Boolean clauses and "special"
* index operators are expanded into clauses that the indexscan machinery * index operators are expanded into clauses that the indexscan machinery
* will know what to do with. RowCompare clauses are simplified if * will know what to do with. RowCompare clauses are simplified if
* necessary to create a clause that is fully checkable by the index. * necessary to create a clause that is fully checkable by the index.
* *
* The input list is ordered by index key, and so the output list is too. * The input clauses are grouped by index key, and so the output is too.
* (The latter is not depended on by any part of the core planner, I believe, * (This is depended on in various places in both planner and executor.)
* but parts of the executor require it, and so do the amcostestimate
* functions.)
*/ */
List * List *
expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
{ {
List *resultquals = NIL; List *resultgroups = NIL;
ListCell *lc; ListCell *lc;
int indexcol; int indexcol;
...@@ -2827,6 +2884,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) ...@@ -2827,6 +2884,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
List *clausegroup = (List *) lfirst(lc); List *clausegroup = (List *) lfirst(lc);
Oid curFamily = index->opfamily[indexcol]; Oid curFamily = index->opfamily[indexcol];
Oid curCollation = index->indexcollations[indexcol]; Oid curCollation = index->indexcollations[indexcol];
List *newgroup = NIL;
ListCell *lc2; ListCell *lc2;
foreach(lc2, clausegroup) foreach(lc2, clausegroup)
...@@ -2844,8 +2902,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) ...@@ -2844,8 +2902,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
index); index);
if (boolqual) if (boolqual)
{ {
resultquals = lappend(resultquals, newgroup = lappend(newgroup,
make_simple_restrictinfo(boolqual)); make_simple_restrictinfo(boolqual));
continue; continue;
} }
} }
...@@ -2856,38 +2914,40 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) ...@@ -2856,38 +2914,40 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
*/ */
if (is_opclause(clause)) if (is_opclause(clause))
{ {
resultquals = list_concat(resultquals, newgroup = list_concat(newgroup,
expand_indexqual_opclause(rinfo, expand_indexqual_opclause(rinfo,
curFamily, curFamily,
curCollation)); curCollation));
} }
else if (IsA(clause, ScalarArrayOpExpr)) else if (IsA(clause, ScalarArrayOpExpr))
{ {
/* no extra work at this time */ /* no extra work at this time */
resultquals = lappend(resultquals, rinfo); newgroup = lappend(newgroup, rinfo);
} }
else if (IsA(clause, RowCompareExpr)) else if (IsA(clause, RowCompareExpr))
{ {
resultquals = lappend(resultquals, newgroup = lappend(newgroup,
expand_indexqual_rowcompare(rinfo, expand_indexqual_rowcompare(rinfo,
index, index,
indexcol)); indexcol));
} }
else if (IsA(clause, NullTest)) else if (IsA(clause, NullTest))
{ {
Assert(index->amsearchnulls); Assert(index->amsearchnulls);
resultquals = lappend(resultquals, newgroup = lappend(newgroup,
make_simple_restrictinfo(clause)); make_simple_restrictinfo(clause));
} }
else else
elog(ERROR, "unsupported indexqual type: %d", elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause)); (int) nodeTag(clause));
} }
resultgroups = lappend(resultgroups, newgroup);
indexcol++; indexcol++;
} }
return resultquals; return resultgroups;
} }
/* /*
...@@ -3054,6 +3114,41 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation) ...@@ -3054,6 +3114,41 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation)
* expand_indexqual_rowcompare --- expand a single indexqual condition * expand_indexqual_rowcompare --- expand a single indexqual condition
* that is a RowCompareExpr * that is a RowCompareExpr
* *
* This is a thin wrapper around adjust_rowcompare_for_index; we export the
* latter so that createplan.c can use it to re-discover which columns of the
* index are used by a row comparison indexqual.
*/
static RestrictInfo *
expand_indexqual_rowcompare(RestrictInfo *rinfo,
IndexOptInfo *index,
int indexcol)
{
RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
Expr *newclause;
List *indexcolnos;
bool var_on_left;
newclause = adjust_rowcompare_for_index(clause,
index,
indexcol,
&indexcolnos,
&var_on_left);
/*
* If we didn't have to change the RowCompareExpr, return the original
* RestrictInfo.
*/
if (newclause == (Expr *) clause)
return rinfo;
/* Else we need a new RestrictInfo */
return make_simple_restrictinfo(newclause);
}
/*
* adjust_rowcompare_for_index --- expand a single indexqual condition
* that is a RowCompareExpr
*
* It's already known that the first column of the row comparison matches * It's already known that the first column of the row comparison matches
* the specified column of the index. We can use additional columns of the * the specified column of the index. We can use additional columns of the
* row comparison as index qualifications, so long as they match the index * row comparison as index qualifications, so long as they match the index
...@@ -3066,13 +3161,23 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation) ...@@ -3066,13 +3161,23 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation)
* even when the original was "<" or ">" --- this is necessary to match all * even when the original was "<" or ">" --- this is necessary to match all
* the rows that could match the original. (We are essentially building a * the rows that could match the original. (We are essentially building a
* lossy version of the row comparison when we do this.) * lossy version of the row comparison when we do this.)
*
* *indexcolnos receives an integer list of the index column numbers (zero
* based) used in the resulting expression. The reason we need to return
* that is that if the index is selected for use, createplan.c will need to
* call this again to extract that list. (This is a bit grotty, but row
* comparison indexquals aren't used enough to justify finding someplace to
* keep the information in the Path representation.) Since createplan.c
* also needs to know which side of the RowCompareExpr is the index side,
* we also return *var_on_left_p rather than re-deducing that there.
*/ */
static RestrictInfo * Expr *
expand_indexqual_rowcompare(RestrictInfo *rinfo, adjust_rowcompare_for_index(RowCompareExpr *clause,
IndexOptInfo *index, IndexOptInfo *index,
int indexcol) int indexcol,
List **indexcolnos,
bool *var_on_left_p)
{ {
RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
bool var_on_left; bool var_on_left;
int op_strategy; int op_strategy;
Oid op_lefttype; Oid op_lefttype;
...@@ -3094,6 +3199,8 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ...@@ -3094,6 +3199,8 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
Assert(var_on_left || Assert(var_on_left ||
match_index_to_operand((Node *) linitial(clause->rargs), match_index_to_operand((Node *) linitial(clause->rargs),
indexcol, index)); indexcol, index));
*var_on_left_p = var_on_left;
expr_op = linitial_oid(clause->opnos); expr_op = linitial_oid(clause->opnos);
if (!var_on_left) if (!var_on_left)
expr_op = get_commutator(expr_op); expr_op = get_commutator(expr_op);
...@@ -3101,6 +3208,10 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ...@@ -3101,6 +3208,10 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
&op_strategy, &op_strategy,
&op_lefttype, &op_lefttype,
&op_righttype); &op_righttype);
/* Initialize returned list of which index columns are used */
*indexcolnos = list_make1_int(indexcol);
/* Build lists of the opfamilies and operator datatypes in case needed */ /* Build lists of the opfamilies and operator datatypes in case needed */
opfamilies = list_make1_oid(index->opfamily[indexcol]); opfamilies = list_make1_oid(index->opfamily[indexcol]);
lefttypes = list_make1_oid(op_lefttype); lefttypes = list_make1_oid(op_lefttype);
...@@ -3147,28 +3258,22 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ...@@ -3147,28 +3258,22 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
break; /* no good, volatile comparison value */ break; /* no good, volatile comparison value */
/* /*
* The Var side can match any column of the index. If the user does * The Var side can match any column of the index.
* something weird like having multiple identical index columns, we
* insist the match be on the first such column, to avoid confusing
* the executor.
*/ */
for (i = 0; i < index->ncolumns; i++) for (i = 0; i < index->ncolumns; i++)
{ {
if (match_index_to_operand(varop, i, index)) if (match_index_to_operand(varop, i, index) &&
get_op_opfamily_strategy(expr_op,
index->opfamily[i]) == op_strategy &&
IndexCollMatchesExprColl(index->indexcollations[i],
lfirst_oid(collids_cell)))
break; break;
} }
if (i >= index->ncolumns) if (i >= index->ncolumns)
break; /* no match found */ break; /* no match found */
/* Now, do we have the right operator for this column? */ /* Add column number to returned list */
if (get_op_opfamily_strategy(expr_op, index->opfamily[i]) *indexcolnos = lappend_int(*indexcolnos, i);
!= op_strategy)
break;
/* Does collation match? */
if (!IndexCollMatchesExprColl(index->indexcollations[i],
lfirst_oid(collids_cell)))
break;
/* Add opfamily and datatypes to lists */ /* Add opfamily and datatypes to lists */
get_op_opfamily_properties(expr_op, index->opfamily[i], false, get_op_opfamily_properties(expr_op, index->opfamily[i], false,
...@@ -3189,7 +3294,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ...@@ -3189,7 +3294,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
/* Return clause as-is if it's all usable as index quals */ /* Return clause as-is if it's all usable as index quals */
if (matching_cols == list_length(clause->opnos)) if (matching_cols == list_length(clause->opnos))
return rinfo; return (Expr *) clause;
/* /*
* We have to generate a subset rowcompare (possibly just one OpExpr). The * We have to generate a subset rowcompare (possibly just one OpExpr). The
...@@ -3260,18 +3365,15 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ...@@ -3260,18 +3365,15 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
matching_cols); matching_cols);
rc->rargs = list_truncate((List *) copyObject(clause->rargs), rc->rargs = list_truncate((List *) copyObject(clause->rargs),
matching_cols); matching_cols);
return make_simple_restrictinfo((Expr *) rc); return (Expr *) rc;
} }
else else
{ {
Expr *opexpr; return make_opclause(linitial_oid(new_ops), BOOLOID, false,
copyObject(linitial(clause->largs)),
opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false, copyObject(linitial(clause->rargs)),
copyObject(linitial(clause->largs)), InvalidOid,
copyObject(linitial(clause->rargs)), linitial_oid(clause->inputcollids));
InvalidOid,
linitial_oid(clause->inputcollids));
return make_simple_restrictinfo(opexpr);
} }
} }
......
...@@ -87,7 +87,7 @@ static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, ...@@ -87,7 +87,7 @@ static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
List *indexquals); List *indexquals);
static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path, static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
List *indexorderbys); List *indexorderbys);
static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index); static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *get_switched_clauses(List *clauses, Relids outerrelids);
static List *order_qual_clauses(PlannerInfo *root, List *clauses); static List *order_qual_clauses(PlannerInfo *root, List *clauses);
static void copy_path_costsize(Plan *dest, Path *src); static void copy_path_costsize(Plan *dest, Path *src);
...@@ -1073,10 +1073,6 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, ...@@ -1073,10 +1073,6 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
* qual preprocessing work is the same for both. Note that the caller tells * qual preprocessing work is the same for both. Note that the caller tells
* us which to build --- we don't look at best_path->path.pathtype, because * us which to build --- we don't look at best_path->path.pathtype, because
* create_bitmap_subplan needs to be able to override the prior decision. * create_bitmap_subplan needs to be able to override the prior decision.
*
* The indexquals list of the path contains implicitly-ANDed qual conditions.
* The list can be empty --- then no index restrictions will be applied during
* the scan.
*/ */
static Scan * static Scan *
create_indexscan_plan(PlannerInfo *root, create_indexscan_plan(PlannerInfo *root,
...@@ -1086,11 +1082,11 @@ create_indexscan_plan(PlannerInfo *root, ...@@ -1086,11 +1082,11 @@ create_indexscan_plan(PlannerInfo *root,
bool indexonly) bool indexonly)
{ {
Scan *scan_plan; Scan *scan_plan;
List *indexquals = best_path->indexquals;
List *indexorderbys = best_path->indexorderbys; List *indexorderbys = best_path->indexorderbys;
Index baserelid = best_path->path.parent->relid; Index baserelid = best_path->path.parent->relid;
Oid indexoid = best_path->indexinfo->indexoid; Oid indexoid = best_path->indexinfo->indexoid;
List *qpqual; List *qpqual;
List *indexquals;
List *stripped_indexquals; List *stripped_indexquals;
List *fixed_indexquals; List *fixed_indexquals;
List *fixed_indexorderbys; List *fixed_indexorderbys;
...@@ -1100,6 +1096,13 @@ create_indexscan_plan(PlannerInfo *root, ...@@ -1100,6 +1096,13 @@ create_indexscan_plan(PlannerInfo *root,
Assert(baserelid > 0); Assert(baserelid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION); Assert(best_path->path.parent->rtekind == RTE_RELATION);
/*
* We need to flatten the indexquals list-of-sublists, since most of the
* processing below doesn't care which index column each qual is
* associated with.
*/
indexquals = flatten_clausegroups_list(best_path->indexquals);
/* /*
* Build "stripped" indexquals structure (no RestrictInfos) to pass to * Build "stripped" indexquals structure (no RestrictInfos) to pass to
* executor as indexqualorig * executor as indexqualorig
...@@ -1108,14 +1111,23 @@ create_indexscan_plan(PlannerInfo *root, ...@@ -1108,14 +1111,23 @@ create_indexscan_plan(PlannerInfo *root,
/* /*
* The executor needs a copy with the indexkey on the left of each clause * The executor needs a copy with the indexkey on the left of each clause
* and with index Vars substituted for table ones. * and with index Vars substituted for table ones. Here we use the
* unflattened list so we can conveniently tell which index column each
* clause is for.
*/ */
fixed_indexquals = fix_indexqual_references(root, best_path, indexquals); fixed_indexquals = fix_indexqual_references(root, best_path,
best_path->indexquals);
/* /*
* Likewise fix up index attr references in the ORDER BY expressions. * Likewise fix up index attr references in the ORDER BY expressions.
*/ */
fixed_indexorderbys = fix_indexorderby_references(root, best_path, indexorderbys); fixed_indexorderbys = fix_indexorderby_references(root, best_path,
indexorderbys);
/*
* Also produce a flat list to become the indexorderbyorig.
*/
indexorderbys = flatten_indexorderbys_list(indexorderbys);
/* /*
* If this is an innerjoin scan, the indexclauses will contain join * If this is an innerjoin scan, the indexclauses will contain join
...@@ -1494,7 +1506,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, ...@@ -1494,7 +1506,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples); clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */ plan->plan_width = 0; /* meaningless */
*qual = get_actual_clauses(ipath->indexclauses); *qual = get_actual_clauses(ipath->indexclauses);
*indexqual = get_actual_clauses(ipath->indexquals); *indexqual = get_actual_clauses(flatten_clausegroups_list(ipath->indexquals));
foreach(l, ipath->indexinfo->indpred) foreach(l, ipath->indexinfo->indpred)
{ {
Expr *pred = (Expr *) lfirst(l); Expr *pred = (Expr *) lfirst(l);
...@@ -2472,7 +2484,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) ...@@ -2472,7 +2484,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
* Adjust indexqual clauses to the form the executor's indexqual * Adjust indexqual clauses to the form the executor's indexqual
* machinery needs. * machinery needs.
* *
* We have four tasks here: * We have five tasks here:
* * Flatten the list-of-sublists structure of indexquals into a simple list.
* * Remove RestrictInfo nodes from the input clauses. * * Remove RestrictInfo nodes from the input clauses.
* * Replace any outer-relation Var or PHV nodes with nestloop Params. * * Replace any outer-relation Var or PHV nodes with nestloop Params.
* (XXX eventually, that responsibility should go elsewhere?) * (XXX eventually, that responsibility should go elsewhere?)
...@@ -2492,96 +2505,129 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, ...@@ -2492,96 +2505,129 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
{ {
IndexOptInfo *index = index_path->indexinfo; IndexOptInfo *index = index_path->indexinfo;
List *fixed_indexquals; List *fixed_indexquals;
ListCell *l; ListCell *lc1;
int indexcol;
fixed_indexquals = NIL; fixed_indexquals = NIL;
foreach(l, indexquals) /* clausegroups must correspond to index columns */
{ Assert(list_length(indexquals) <= index->ncolumns);
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Node *clause;
Assert(IsA(rinfo, RestrictInfo));
/* indexcol = 0;
* Replace any outer-relation variables with nestloop params. foreach(lc1, indexquals)
* {
* This also makes a copy of the clause, so it's safe to modify it List *clausegroup = (List *) lfirst(lc1);
* in-place below. ListCell *lc2;
*/
clause = replace_nestloop_params(root, (Node *) rinfo->clause);
if (IsA(clause, OpExpr)) foreach(lc2, clausegroup)
{ {
OpExpr *op = (OpExpr *) clause; RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
Node *clause;
if (list_length(op->args) != 2) Assert(IsA(rinfo, RestrictInfo));
elog(ERROR, "indexqual clause is not binary opclause");
/* /*
* Check to see if the indexkey is on the right; if so, commute * Replace any outer-relation variables with nestloop params.
* the clause. The indexkey should be the side that refers to *
* (only) the base relation. * This also makes a copy of the clause, so it's safe to modify it
* in-place below.
*/ */
if (!bms_equal(rinfo->left_relids, index->rel->relids)) clause = replace_nestloop_params(root, (Node *) rinfo->clause);
CommuteOpExpr(op);
/* if (IsA(clause, OpExpr))
* Now, determine which index attribute this is and change the {
* indexkey operand as needed. OpExpr *op = (OpExpr *) clause;
*/
linitial(op->args) = fix_indexqual_operand(linitial(op->args),
index);
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
ListCell *lc;
/* if (list_length(op->args) != 2)
* Check to see if the indexkey is on the right; if so, commute elog(ERROR, "indexqual clause is not binary opclause");
* the clause. The indexkey should be the side that refers to
* (only) the base relation.
*/
if (!bms_overlap(pull_varnos(linitial(rc->largs)),
index->rel->relids))
CommuteRowCompareExpr(rc);
/* /*
* For each column in the row comparison, determine which index * Check to see if the indexkey is on the right; if so,
* attribute this is and change the indexkey operand as needed. * commute the clause. The indexkey should be the side that
*/ * refers to (only) the base relation.
foreach(lc, rc->largs) */
if (!bms_equal(rinfo->left_relids, index->rel->relids))
CommuteOpExpr(op);
/*
* Now replace the indexkey expression with an index Var.
*/
linitial(op->args) = fix_indexqual_operand(linitial(op->args),
index,
indexcol);
}
else if (IsA(clause, RowCompareExpr))
{ {
lfirst(lc) = fix_indexqual_operand(lfirst(lc), RowCompareExpr *rc = (RowCompareExpr *) clause;
index); Expr *newrc;
List *indexcolnos;
bool var_on_left;
ListCell *lca,
*lci;
/*
* Re-discover which index columns are used in the rowcompare.
*/
newrc = adjust_rowcompare_for_index(rc,
index,
indexcol,
&indexcolnos,
&var_on_left);
/*
* Trouble if adjust_rowcompare_for_index thought the
* RowCompareExpr didn't match the index as-is; the clause
* should have gone through that routine already.
*/
if (newrc != (Expr *) rc)
elog(ERROR, "inconsistent results from adjust_rowcompare_for_index");
/*
* Check to see if the indexkey is on the right; if so,
* commute the clause.
*/
if (!var_on_left)
CommuteRowCompareExpr(rc);
/*
* Now replace the indexkey expressions with index Vars.
*/
Assert(list_length(rc->largs) == list_length(indexcolnos));
forboth(lca, rc->largs, lci, indexcolnos)
{
lfirst(lca) = fix_indexqual_operand(lfirst(lca),
index,
lfirst_int(lci));
}
} }
} else if (IsA(clause, ScalarArrayOpExpr))
else if (IsA(clause, ScalarArrayOpExpr)) {
{ ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
/* Never need to commute... */ /* Never need to commute... */
/* /* Replace the indexkey expression with an index Var. */
* Determine which index attribute this is and change the indexkey linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
* operand as needed. index,
*/ indexcol);
linitial(saop->args) = fix_indexqual_operand(linitial(saop->args), }
index); else if (IsA(clause, NullTest))
} {
else if (IsA(clause, NullTest)) NullTest *nt = (NullTest *) clause;
{
NullTest *nt = (NullTest *) clause; /* Replace the indexkey expression with an index Var. */
nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg,
index,
indexcol);
}
else
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg, fixed_indexquals = lappend(fixed_indexquals, clause);
index);
} }
else
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
fixed_indexquals = lappend(fixed_indexquals, clause); indexcol++;
} }
return fixed_indexquals; return fixed_indexquals;
...@@ -2593,7 +2639,7 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, ...@@ -2593,7 +2639,7 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
* machinery needs. * machinery needs.
* *
* This is a simplified version of fix_indexqual_references. The input does * This is a simplified version of fix_indexqual_references. The input does
* not have RestrictInfo nodes, and we assume that indxqual.c already * not have RestrictInfo nodes, and we assume that indxpath.c already
* commuted the clauses to put the index keys on the left. Also, we don't * commuted the clauses to put the index keys on the left. Also, we don't
* bother to support any cases except simple OpExprs, since nothing else * bother to support any cases except simple OpExprs, since nothing else
* is allowed for ordering operators. * is allowed for ordering operators.
...@@ -2604,41 +2650,62 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path, ...@@ -2604,41 +2650,62 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
{ {
IndexOptInfo *index = index_path->indexinfo; IndexOptInfo *index = index_path->indexinfo;
List *fixed_indexorderbys; List *fixed_indexorderbys;
ListCell *l; ListCell *lc1;
fixed_indexorderbys = NIL; fixed_indexorderbys = NIL;
foreach(l, indexorderbys) foreach(lc1, indexorderbys)
{ {
Node *clause = (Node *) lfirst(l); List *percollists = (List *) lfirst(lc1);
ListCell *lc2;
int indexcol;
/* /* percollists must correspond to index columns */
* Replace any outer-relation variables with nestloop params. Assert(list_length(percollists) <= index->ncolumns);
*
* This also makes a copy of the clause, so it's safe to modify it
* in-place below.
*/
clause = replace_nestloop_params(root, clause);
if (IsA(clause, OpExpr)) indexcol = 0;
foreach(lc2, percollists)
{ {
OpExpr *op = (OpExpr *) clause; List *percollist = (List *) lfirst(lc2);
if (list_length(op->args) != 2) if (percollist != NIL)
elog(ERROR, "indexorderby clause is not binary opclause"); {
Node *clause = (Node *) linitial(percollist);
/* /* Should have only one clause per index column */
* Now, determine which index attribute this is and change the Assert(list_length(percollist) == 1);
* indexkey operand as needed.
*/ /*
linitial(op->args) = fix_indexqual_operand(linitial(op->args), * Replace any outer-relation variables with nestloop params.
index); *
} * This also makes a copy of the clause, so it's safe to
else * modify it in-place below.
elog(ERROR, "unsupported indexorderby type: %d", */
(int) nodeTag(clause)); clause = replace_nestloop_params(root, clause);
if (IsA(clause, OpExpr))
{
OpExpr *op = (OpExpr *) clause;
if (list_length(op->args) != 2)
elog(ERROR, "indexorderby clause is not binary opclause");
/*
* Now replace the indexkey expression with an index Var.
*/
linitial(op->args) = fix_indexqual_operand(linitial(op->args),
index,
indexcol);
}
else
elog(ERROR, "unsupported indexorderby type: %d",
(int) nodeTag(clause));
fixed_indexorderbys = lappend(fixed_indexorderbys, clause);
}
fixed_indexorderbys = lappend(fixed_indexorderbys, clause); indexcol++;
}
} }
return fixed_indexorderbys; return fixed_indexorderbys;
...@@ -2650,9 +2717,12 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path, ...@@ -2650,9 +2717,12 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
* *
* We represent index keys by Var nodes having varno == INDEX_VAR and varattno * We represent index keys by Var nodes having varno == INDEX_VAR and varattno
* equal to the index's attribute number (index column position). * equal to the index's attribute number (index column position).
*
* Most of the code here is just for sanity cross-checking that the given
* expression actually matches the index column it's claimed to.
*/ */
static Node * static Node *
fix_indexqual_operand(Node *node, IndexOptInfo *index) fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
{ {
Var *result; Var *result;
int pos; int pos;
...@@ -2664,55 +2734,56 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index) ...@@ -2664,55 +2734,56 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index)
if (IsA(node, RelabelType)) if (IsA(node, RelabelType))
node = (Node *) ((RelabelType *) node)->arg; node = (Node *) ((RelabelType *) node)->arg;
if (IsA(node, Var) && Assert(indexcol >= 0 && indexcol < index->ncolumns);
((Var *) node)->varno == index->rel->relid)
{
/* Try to match against simple index columns */
int varatt = ((Var *) node)->varattno;
if (varatt != 0) if (index->indexkeys[indexcol] != 0)
{
/* It's a simple index column */
if (IsA(node, Var) &&
((Var *) node)->varno == index->rel->relid &&
((Var *) node)->varattno == index->indexkeys[indexcol])
{ {
for (pos = 0; pos < index->ncolumns; pos++) result = (Var *) copyObject(node);
{ result->varno = INDEX_VAR;
if (index->indexkeys[pos] == varatt) result->varattno = indexcol + 1;
{ return (Node *) result;
result = (Var *) copyObject(node);
result->varno = INDEX_VAR;
result->varattno = pos + 1;
return (Node *) result;
}
}
} }
else
elog(ERROR, "index key does not match expected index column");
} }
/* Try to match against index expressions */ /* It's an index expression, so find and cross-check the expression */
indexpr_item = list_head(index->indexprs); indexpr_item = list_head(index->indexprs);
for (pos = 0; pos < index->ncolumns; pos++) for (pos = 0; pos < index->ncolumns; pos++)
{ {
if (index->indexkeys[pos] == 0) if (index->indexkeys[pos] == 0)
{ {
Node *indexkey;
if (indexpr_item == NULL) if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list"); elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item); if (pos == indexcol)
if (indexkey && IsA(indexkey, RelabelType))
indexkey = (Node *) ((RelabelType *) indexkey)->arg;
if (equal(node, indexkey))
{ {
/* Found a match */ Node *indexkey;
result = makeVar(INDEX_VAR, pos + 1,
exprType(lfirst(indexpr_item)), -1, indexkey = (Node *) lfirst(indexpr_item);
exprCollation(lfirst(indexpr_item)), if (indexkey && IsA(indexkey, RelabelType))
0); indexkey = (Node *) ((RelabelType *) indexkey)->arg;
return (Node *) result; if (equal(node, indexkey))
{
result = makeVar(INDEX_VAR, indexcol + 1,
exprType(lfirst(indexpr_item)), -1,
exprCollation(lfirst(indexpr_item)),
0);
return (Node *) result;
}
else
elog(ERROR, "index key does not match expected index column");
} }
indexpr_item = lnext(indexpr_item); indexpr_item = lnext(indexpr_item);
} }
} }
/* Ooops... */ /* Ooops... */
elog(ERROR, "node is not an index attribute"); elog(ERROR, "index key does not match expected index column");
return NULL; /* keep compiler quiet */ return NULL; /* keep compiler quiet */
} }
......
...@@ -412,8 +412,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel) ...@@ -412,8 +412,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
* 'index' is a usable index. * 'index' is a usable index.
* 'clause_groups' is a list of lists of RestrictInfo nodes * 'clause_groups' is a list of lists of RestrictInfo nodes
* to be used as index qual conditions in the scan. * to be used as index qual conditions in the scan.
* 'indexorderbys' is a list of bare expressions (no RestrictInfos) * 'indexorderbys' is a list of lists of lists of bare expressions (not
* to be used as index ordering operators in the scan. * RestrictInfos) to be used as index ordering operators.
* 'pathkeys' describes the ordering of the path. * 'pathkeys' describes the ordering of the path.
* 'indexscandir' is ForwardScanDirection or BackwardScanDirection * 'indexscandir' is ForwardScanDirection or BackwardScanDirection
* for an ordered index, or NoMovementScanDirection for * for an ordered index, or NoMovementScanDirection for
......
...@@ -630,7 +630,7 @@ extract_actual_join_clauses(List *restrictinfo_list, ...@@ -630,7 +630,7 @@ extract_actual_join_clauses(List *restrictinfo_list,
* being used in an inner indexscan need not be checked again at the join. * being used in an inner indexscan need not be checked again at the join.
* *
* "Redundant" means either equal() or derived from the same EquivalenceClass. * "Redundant" means either equal() or derived from the same EquivalenceClass.
* We have to check the latter because indxqual.c may select different derived * We have to check the latter because indxpath.c may select different derived
* clauses than were selected by generate_join_implied_equalities(). * clauses than were selected by generate_join_implied_equalities().
* *
* Note that we are *not* checking for local redundancies within the given * Note that we are *not* checking for local redundancies within the given
......
...@@ -5991,6 +5991,14 @@ genericcostestimate(PlannerInfo *root, ...@@ -5991,6 +5991,14 @@ genericcostestimate(PlannerInfo *root,
List *selectivityQuals; List *selectivityQuals;
ListCell *l; ListCell *l;
/*
* For our purposes here, it doesn't matter which index columns the
* individual quals and order-by expressions go with, so flatten the
* lists for convenience.
*/
indexQuals = flatten_clausegroups_list(indexQuals);
indexOrderBys = flatten_indexorderbys_list(indexOrderBys);
/*---------- /*----------
* If the index is partial, AND the index predicate with the explicitly * If the index is partial, AND the index predicate with the explicitly
* given indexquals to produce a more accurate idea of the index * given indexquals to produce a more accurate idea of the index
...@@ -6022,7 +6030,7 @@ genericcostestimate(PlannerInfo *root, ...@@ -6022,7 +6030,7 @@ genericcostestimate(PlannerInfo *root,
if (!predicate_implied_by(oneQual, indexQuals)) if (!predicate_implied_by(oneQual, indexQuals))
predExtraQuals = list_concat(predExtraQuals, oneQual); predExtraQuals = list_concat(predExtraQuals, oneQual);
} }
/* list_concat avoids modifying the passed-in indexQuals list */ /* list_concat avoids modifying the indexQuals list */
selectivityQuals = list_concat(predExtraQuals, indexQuals); selectivityQuals = list_concat(predExtraQuals, indexQuals);
} }
else else
...@@ -6250,7 +6258,7 @@ btcostestimate(PG_FUNCTION_ARGS) ...@@ -6250,7 +6258,7 @@ btcostestimate(PG_FUNCTION_ARGS)
bool found_saop; bool found_saop;
bool found_is_null_op; bool found_is_null_op;
double num_sa_scans; double num_sa_scans;
ListCell *l; ListCell *lc1;
/* /*
* For a btree scan, only leading '=' quals plus inequality quals for the * For a btree scan, only leading '=' quals plus inequality quals for the
...@@ -6259,8 +6267,7 @@ btcostestimate(PG_FUNCTION_ARGS) ...@@ -6259,8 +6267,7 @@ btcostestimate(PG_FUNCTION_ARGS)
* the index scan). Additional quals can suppress visits to the heap, so * the index scan). Additional quals can suppress visits to the heap, so
* it's OK to count them in indexSelectivity, but they should not count * it's OK to count them in indexSelectivity, but they should not count
* for estimating numIndexTuples. So we must examine the given indexQuals * for estimating numIndexTuples. So we must examine the given indexQuals
* to find out which ones count as boundary quals. We rely on the * to find out which ones count as boundary quals.
* knowledge that they are given in index column order.
* *
* For a RowCompareExpr, we consider only the first column, just as * For a RowCompareExpr, we consider only the first column, just as
* rowcomparesel() does. * rowcomparesel() does.
...@@ -6270,120 +6277,119 @@ btcostestimate(PG_FUNCTION_ARGS) ...@@ -6270,120 +6277,119 @@ btcostestimate(PG_FUNCTION_ARGS)
* considered to act the same as it normally does. * considered to act the same as it normally does.
*/ */
indexBoundQuals = NIL; indexBoundQuals = NIL;
indexcol = 0;
eqQualHere = false; eqQualHere = false;
found_saop = false; found_saop = false;
found_is_null_op = false; found_is_null_op = false;
num_sa_scans = 1; num_sa_scans = 1;
foreach(l, indexQuals)
/* clausegroups must correspond to index columns */
Assert(list_length(indexQuals) <= index->ncolumns);
indexcol = 0;
foreach(lc1, indexQuals)
{ {
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); List *clausegroup = (List *) lfirst(lc1);
Expr *clause; ListCell *lc2;
Node *leftop,
*rightop;
Oid clause_op;
int op_strategy;
bool is_null_op = false;
Assert(IsA(rinfo, RestrictInfo)); eqQualHere = false;
clause = rinfo->clause;
if (IsA(clause, OpExpr))
{
leftop = get_leftop(clause);
rightop = get_rightop(clause);
clause_op = ((OpExpr *) clause)->opno;
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
leftop = (Node *) linitial(rc->largs); foreach(lc2, clausegroup)
rightop = (Node *) linitial(rc->rargs);
clause_op = linitial_oid(rc->opnos);
}
else if (IsA(clause, ScalarArrayOpExpr))
{ {
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
Expr *clause;
Node *leftop,
*rightop;
Oid clause_op;
int op_strategy;
bool is_null_op = false;
Assert(IsA(rinfo, RestrictInfo));
clause = rinfo->clause;
if (IsA(clause, OpExpr))
{
leftop = get_leftop(clause);
rightop = get_rightop(clause);
clause_op = ((OpExpr *) clause)->opno;
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
leftop = (Node *) linitial(saop->args); leftop = (Node *) linitial(rc->largs);
rightop = (Node *) lsecond(saop->args); rightop = (Node *) linitial(rc->rargs);
clause_op = saop->opno; clause_op = linitial_oid(rc->opnos);
found_saop = true; }
} else if (IsA(clause, ScalarArrayOpExpr))
else if (IsA(clause, NullTest)) {
{ ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
NullTest *nt = (NullTest *) clause;
leftop = (Node *) nt->arg; leftop = (Node *) linitial(saop->args);
rightop = NULL; rightop = (Node *) lsecond(saop->args);
clause_op = InvalidOid; clause_op = saop->opno;
if (nt->nulltesttype == IS_NULL) found_saop = true;
}
else if (IsA(clause, NullTest))
{ {
found_is_null_op = true; NullTest *nt = (NullTest *) clause;
is_null_op = true;
leftop = (Node *) nt->arg;
rightop = NULL;
clause_op = InvalidOid;
if (nt->nulltesttype == IS_NULL)
{
found_is_null_op = true;
is_null_op = true;
}
} }
} else
else {
{ elog(ERROR, "unsupported indexqual type: %d",
elog(ERROR, "unsupported indexqual type: %d", (int) nodeTag(clause));
(int) nodeTag(clause)); continue; /* keep compiler quiet */
continue; /* keep compiler quiet */ }
}
if (match_index_to_operand(leftop, indexcol, index))
{
/* clause_op is correct */
}
else if (match_index_to_operand(rightop, indexcol, index))
{
/* Must flip operator to get the opfamily member */
clause_op = get_commutator(clause_op);
}
else
{
/* Must be past the end of quals for indexcol, try next */
if (!eqQualHere)
break; /* done if no '=' qual for indexcol */
indexcol++;
eqQualHere = false;
if (match_index_to_operand(leftop, indexcol, index)) if (match_index_to_operand(leftop, indexcol, index))
{ {
/* clause_op is correct */ /* clause_op is correct */
} }
else if (match_index_to_operand(rightop, indexcol, index)) else
{ {
Assert(match_index_to_operand(rightop, indexcol, index));
/* Must flip operator to get the opfamily member */ /* Must flip operator to get the opfamily member */
clause_op = get_commutator(clause_op); clause_op = get_commutator(clause_op);
} }
else
/* check for equality operator */
if (OidIsValid(clause_op))
{ {
/* No quals for new indexcol, so we are done */ op_strategy = get_op_opfamily_strategy(clause_op,
break;
}
}
/* check for equality operator */
if (OidIsValid(clause_op))
{
op_strategy = get_op_opfamily_strategy(clause_op,
index->opfamily[indexcol]); index->opfamily[indexcol]);
Assert(op_strategy != 0); /* not a member of opfamily?? */ Assert(op_strategy != 0); /* not a member of opfamily?? */
if (op_strategy == BTEqualStrategyNumber) if (op_strategy == BTEqualStrategyNumber)
eqQualHere = true;
}
else if (is_null_op)
{
/* IS NULL is like = for selectivity determination */
eqQualHere = true; eqQualHere = true;
} }
else if (is_null_op) /* count up number of SA scans induced by indexBoundQuals only */
{ if (IsA(clause, ScalarArrayOpExpr))
/* IS NULL is like = for purposes of selectivity determination */ {
eqQualHere = true; ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
} int alength = estimate_array_length(lsecond(saop->args));
/* count up number of SA scans induced by indexBoundQuals only */
if (IsA(clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
int alength = estimate_array_length(lsecond(saop->args));
if (alength > 1) if (alength > 1)
num_sa_scans *= alength; num_sa_scans *= alength;
}
indexBoundQuals = lappend(indexBoundQuals, rinfo);
} }
indexBoundQuals = lappend(indexBoundQuals, rinfo);
/* Done with this indexcol, continue to next only if it had = qual */
if (!eqQualHere)
break;
indexcol++;
} }
/* /*
...@@ -6393,7 +6399,7 @@ btcostestimate(PG_FUNCTION_ARGS) ...@@ -6393,7 +6399,7 @@ btcostestimate(PG_FUNCTION_ARGS)
* NullTest invalidates that theory, even though it sets eqQualHere. * NullTest invalidates that theory, even though it sets eqQualHere.
*/ */
if (index->unique && if (index->unique &&
indexcol == index->ncolumns - 1 && indexcol == index->ncolumns &&
eqQualHere && eqQualHere &&
!found_saop && !found_saop &&
!found_is_null_op) !found_is_null_op)
...@@ -6924,6 +6930,14 @@ gincostestimate(PG_FUNCTION_ARGS) ...@@ -6924,6 +6930,14 @@ gincostestimate(PG_FUNCTION_ARGS)
Relation indexRel; Relation indexRel;
GinStatsData ginStats; GinStatsData ginStats;
/*
* For our purposes here, it doesn't matter which index columns the
* individual quals and order-by expressions go with, so flatten the
* lists for convenience.
*/
indexQuals = flatten_clausegroups_list(indexQuals);
indexOrderBys = flatten_indexorderbys_list(indexOrderBys);
/* /*
* Obtain statistic information from the meta page * Obtain statistic information from the meta page
*/ */
...@@ -6980,7 +6994,7 @@ gincostestimate(PG_FUNCTION_ARGS) ...@@ -6980,7 +6994,7 @@ gincostestimate(PG_FUNCTION_ARGS)
if (!predicate_implied_by(oneQual, indexQuals)) if (!predicate_implied_by(oneQual, indexQuals))
predExtraQuals = list_concat(predExtraQuals, oneQual); predExtraQuals = list_concat(predExtraQuals, oneQual);
} }
/* list_concat avoids modifying the passed-in indexQuals list */ /* list_concat avoids modifying the indexQuals list */
selectivityQuals = list_concat(predExtraQuals, indexQuals); selectivityQuals = list_concat(predExtraQuals, indexQuals);
} }
else else
......
...@@ -659,18 +659,25 @@ typedef struct Path ...@@ -659,18 +659,25 @@ typedef struct Path
* AND semantics across the list. Each clause is a RestrictInfo node from * AND semantics across the list. Each clause is a RestrictInfo node from
* the query's WHERE or JOIN conditions. * the query's WHERE or JOIN conditions.
* *
* 'indexquals' has the same structure as 'indexclauses', but it contains * 'indexquals' is a list of sub-lists of the actual index qual conditions
* the actual indexqual conditions that can be used with the index. * that can be used with the index. There is one possibly-empty sub-list
* In simple cases this is identical to 'indexclauses', but when special * for each index column (but empty sub-lists for trailing columns can be
* indexable operators appear in 'indexclauses', they are replaced by the * omitted). The qual conditions are RestrictInfos, and in simple cases
* derived indexscannable conditions in 'indexquals'. * are the same RestrictInfos that appear in the flat indexclauses list.
* * But when special indexable operators appear in 'indexclauses', they are
* 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have * replaced by their derived indexscannable conditions in 'indexquals'.
* been found to be usable as ordering operators for an amcanorderbyop index. * Note that an entirely empty indexquals list denotes a full-index scan.
* Note that these are not RestrictInfos, just bare expressions, since they *
* generally won't yield booleans. The list will match the path's pathkeys. * 'indexorderbys', if not NIL, is a list of lists of lists of ORDER BY
* Also, unlike the case for quals, it's guaranteed that each expression has * expressions that have been found to be usable as ordering operators for an
* the index key on the left side of the operator. * amcanorderbyop index. These are not RestrictInfos, just bare expressions,
* since they generally won't yield booleans. Also, unlike the case for
* quals, it's guaranteed that each expression has the index key on the left
* side of the operator. The top list has one entry per pathkey in the
* path's pathkeys, and the sub-lists have one sub-sublist per index column.
* This representation is a bit of overkill, since there will be only one
* actual expression per pathkey, but it's convenient because each sub-list
* has the same structure as the indexquals list.
* *
* 'isjoininner' is TRUE if the path is a nestloop inner scan (that is, * 'isjoininner' is TRUE if the path is a nestloop inner scan (that is,
* some of the index conditions are join rather than restriction clauses). * some of the index conditions are join rather than restriction clauses).
......
...@@ -61,6 +61,12 @@ extern List *expand_indexqual_conditions(IndexOptInfo *index, ...@@ -61,6 +61,12 @@ extern List *expand_indexqual_conditions(IndexOptInfo *index,
List *clausegroups); List *clausegroups);
extern void check_partial_indexes(PlannerInfo *root, RelOptInfo *rel); extern void check_partial_indexes(PlannerInfo *root, RelOptInfo *rel);
extern List *flatten_clausegroups_list(List *clausegroups); extern List *flatten_clausegroups_list(List *clausegroups);
extern List *flatten_indexorderbys_list(List *indexorderbys);
extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
IndexOptInfo *index,
int indexcol,
List **indexcolnos,
bool *var_on_left_p);
/* /*
* orindxpath.c * orindxpath.c
......
...@@ -2459,3 +2459,27 @@ RESET enable_seqscan; ...@@ -2459,3 +2459,27 @@ RESET enable_seqscan;
RESET enable_indexscan; RESET enable_indexscan;
RESET enable_bitmapscan; RESET enable_bitmapscan;
DROP TABLE onek_with_null; DROP TABLE onek_with_null;
--
-- Check behavior with duplicate index column contents
--
CREATE TABLE dupindexcols AS
SELECT unique1 as id, stringu2::text as f1 FROM tenk1;
CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops);
VACUUM ANALYZE dupindexcols;
EXPLAIN (COSTS OFF)
SELECT count(*) FROM dupindexcols
WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
QUERY PLAN
---------------------------------------------------------------------------------
Aggregate
-> Index Only Scan using dupindexcols_i on dupindexcols
Index Cond: ((f1 > 'LX'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text))
(3 rows)
SELECT count(*) FROM dupindexcols
WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
count
-------
500
(1 row)
...@@ -39,6 +39,7 @@ SELECT relname, relhasindex ...@@ -39,6 +39,7 @@ SELECT relname, relhasindex
default_tbl | f default_tbl | f
defaultexpr_tbl | f defaultexpr_tbl | f
dept | f dept | f
dupindexcols | t
e_star | f e_star | f
emp | f emp | f
equipment_r | f equipment_r | f
...@@ -164,7 +165,7 @@ SELECT relname, relhasindex ...@@ -164,7 +165,7 @@ SELECT relname, relhasindex
timetz_tbl | f timetz_tbl | f
tinterval_tbl | f tinterval_tbl | f
varchar_tbl | f varchar_tbl | f
(153 rows) (154 rows)
-- --
-- another sanity check: every system catalog that has OIDs should have -- another sanity check: every system catalog that has OIDs should have
......
...@@ -610,6 +610,7 @@ SELECT user_relns() AS user_relns ...@@ -610,6 +610,7 @@ SELECT user_relns() AS user_relns
default_tbl default_tbl
defaultexpr_tbl defaultexpr_tbl
dept dept
dupindexcols
e_star e_star
emp emp
equipment_r equipment_r
...@@ -685,7 +686,7 @@ SELECT user_relns() AS user_relns ...@@ -685,7 +686,7 @@ SELECT user_relns() AS user_relns
toyemp toyemp
varchar_tbl varchar_tbl
xacttest xacttest
(107 rows) (108 rows)
SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
name name
......
...@@ -804,3 +804,18 @@ RESET enable_indexscan; ...@@ -804,3 +804,18 @@ RESET enable_indexscan;
RESET enable_bitmapscan; RESET enable_bitmapscan;
DROP TABLE onek_with_null; DROP TABLE onek_with_null;
--
-- Check behavior with duplicate index column contents
--
CREATE TABLE dupindexcols AS
SELECT unique1 as id, stringu2::text as f1 FROM tenk1;
CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops);
VACUUM ANALYZE dupindexcols;
EXPLAIN (COSTS OFF)
SELECT count(*) FROM dupindexcols
WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
SELECT count(*) FROM dupindexcols
WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment