* Determines and returns the cost of scanning a relation using an index.
*
* 'index' is the index to be used
- * 'indexQuals' is the list of applicable qual clauses (implicit AND semantics)
- * 'indexOrderBys' is the list of ORDER BY operators for amcanorderbyop indexes
+ * 'indexQuals' is a list of lists of applicable qual clauses (implicit AND
+ * 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
* '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
* additional fields of the IndexPath besides startup_cost and total_cost.
* 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
- * bare expressions.
+ * indexQuals is a list of lists of RestrictInfo nodes, but indexOrderBys
+ * is a list of lists of lists of bare expressions.
*
* NOTE: 'indexQuals' must contain only clauses usable as index restrictions.
* Any additional quals evaluated as qpquals may reduce the number of returned
* 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
* 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,
* but *found_clause is set TRUE only if we used at least one clause from
* column C, and no clauses use column B.
*
* Note: in some circumstances we may find the same RestrictInfos coming
- * from multiple places. Defend against redundant outputs by using
- * list_append_unique_ptr (pointer equality should be good enough).
+ * from multiple places. Defend against redundant outputs by keeping a side
+ * 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 *
group_clauses_by_indexkey(IndexOptInfo *index,
bool *found_clause)
{
List *clausegroup_list = NIL;
+ List *used_clauses = NIL;
bool found_outer_clause = false;
int indexcol;
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Assert(IsA(rinfo, RestrictInfo));
+ if (list_member_ptr(used_clauses, rinfo))
+ continue;
if (match_clause_to_indexcol(index,
indexcol,
rinfo,
outer_relids,
saop_control))
{
- clausegroup = list_append_unique_ptr(clausegroup, rinfo);
+ clausegroup = lappend(clausegroup, rinfo);
+ used_clauses = lappend(used_clauses, rinfo);
if (saop_control != SAOP_REQUIRE ||
IsA(rinfo->clause, ScalarArrayOpExpr))
*found_clause = true;
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Assert(IsA(rinfo, RestrictInfo));
+ if (list_member_ptr(used_clauses, rinfo))
+ continue;
if (match_clause_to_indexcol(index,
indexcol,
rinfo,
outer_relids,
saop_control))
{
- clausegroup = list_append_unique_ptr(clausegroup, rinfo);
+ clausegroup = lappend(clausegroup, rinfo);
+ used_clauses = lappend(used_clauses, rinfo);
found_outer_clause = true;
}
}
clausegroup_list = lappend(clausegroup_list, clausegroup);
}
+ list_free(used_clauses);
+
if (!*found_clause && !found_outer_clause)
return NIL; /* no indexable clauses anywhere */
* target index column. This is sufficient to guarantee that some index
* condition can be constructed from the RowCompareExpr --- whether the
* 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
* the clause is of the form "indexkey op ANY (arrayconst)". Since not
* Test whether an index can produce output ordered according to the
* given pathkeys using "ordering operators".
*
- * If it can, return a list of suitable ORDER BY expressions, each of the form
- * "indexedcol operator pseudoconstant". If not, return NIL.
+ * If it can, return a list of lists of lists of ORDER BY expressions,
+ * 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 *
match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
{
- List *orderbyexprs = NIL;
+ List *orderbylists = NIL;
ListCell *lc1;
/* Only indexes with the amcanorderbyop property are interesting here */
pathkey->pk_opfamily);
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;
break;
}
return NIL;
}
- return orderbyexprs; /* success! */
+ return orderbylists; /* success! */
}
/*
* Given a list of lists of RestrictInfos, flatten it to a list
* of RestrictInfos.
*
- * This is used to flatten out the result of group_clauses_by_indexkey()
- * to produce an indexclauses list. The original list structure mustn't
- * be altered, but it's OK to share copies of the underlying RestrictInfos.
+ * This is used to flatten out a list of sublists of index clauses (such as
+ * the result of group_clauses_by_indexkey()) into a single list, for use
+ * 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 *
flatten_clausegroups_list(List *clausegroups)
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 ----
* converted into boolean equality operators.
*
* expand_indexqual_conditions() converts a list of lists of RestrictInfo
- * nodes (with implicit AND semantics across list elements) into
- * a list of clauses that the executor can actually handle. For operators
+ * nodes (with implicit AND semantics across list elements) into a list of
+ * 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,
* but clauses recognized by match_special_index_operator() or
* match_boolean_index_clause() must be converted into one or more "regular"
/*
* 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
* opfamily) are passed through unchanged. Boolean clauses and "special"
* index operators are expanded into clauses that the indexscan machinery
* will know what to do with. RowCompare clauses are simplified if
* 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 latter is not depended on by any part of the core planner, I believe,
- * but parts of the executor require it, and so do the amcostestimate
- * functions.)
+ * The input clauses are grouped by index key, and so the output is too.
+ * (This is depended on in various places in both planner and executor.)
*/
List *
expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
{
- List *resultquals = NIL;
+ List *resultgroups = NIL;
ListCell *lc;
int indexcol;
List *clausegroup = (List *) lfirst(lc);
Oid curFamily = index->opfamily[indexcol];
Oid curCollation = index->indexcollations[indexcol];
+ List *newgroup = NIL;
ListCell *lc2;
foreach(lc2, clausegroup)
index);
if (boolqual)
{
- resultquals = lappend(resultquals,
- make_simple_restrictinfo(boolqual));
+ newgroup = lappend(newgroup,
+ make_simple_restrictinfo(boolqual));
continue;
}
}
*/
if (is_opclause(clause))
{
- resultquals = list_concat(resultquals,
- expand_indexqual_opclause(rinfo,
- curFamily,
+ newgroup = list_concat(newgroup,
+ expand_indexqual_opclause(rinfo,
+ curFamily,
curCollation));
}
else if (IsA(clause, ScalarArrayOpExpr))
{
/* no extra work at this time */
- resultquals = lappend(resultquals, rinfo);
+ newgroup = lappend(newgroup, rinfo);
}
else if (IsA(clause, RowCompareExpr))
{
- resultquals = lappend(resultquals,
- expand_indexqual_rowcompare(rinfo,
- index,
- indexcol));
+ newgroup = lappend(newgroup,
+ expand_indexqual_rowcompare(rinfo,
+ index,
+ indexcol));
}
else if (IsA(clause, NullTest))
{
Assert(index->amsearchnulls);
- resultquals = lappend(resultquals,
- make_simple_restrictinfo(clause));
+ newgroup = lappend(newgroup,
+ make_simple_restrictinfo(clause));
}
else
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
}
+ resultgroups = lappend(resultgroups, newgroup);
+
indexcol++;
}
- return resultquals;
+ return resultgroups;
}
/*
* expand_indexqual_rowcompare --- expand a single indexqual condition
* 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
* 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
* even when the original was "<" or ">" --- this is necessary to match all
* the rows that could match the original. (We are essentially building a
* 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 *
-expand_indexqual_rowcompare(RestrictInfo *rinfo,
+Expr *
+adjust_rowcompare_for_index(RowCompareExpr *clause,
IndexOptInfo *index,
- int indexcol)
+ int indexcol,
+ List **indexcolnos,
+ bool *var_on_left_p)
{
- RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
bool var_on_left;
int op_strategy;
Oid op_lefttype;
Assert(var_on_left ||
match_index_to_operand((Node *) linitial(clause->rargs),
indexcol, index));
+ *var_on_left_p = var_on_left;
+
expr_op = linitial_oid(clause->opnos);
if (!var_on_left)
expr_op = get_commutator(expr_op);
&op_strategy,
&op_lefttype,
&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 */
opfamilies = list_make1_oid(index->opfamily[indexcol]);
lefttypes = list_make1_oid(op_lefttype);
break; /* no good, volatile comparison value */
/*
- * The Var side can match any column of the index. If the user does
- * something weird like having multiple identical index columns, we
- * insist the match be on the first such column, to avoid confusing
- * the executor.
+ * The Var side can match any column of the index.
*/
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;
}
if (i >= index->ncolumns)
break; /* no match found */
- /* Now, do we have the right operator for this column? */
- if (get_op_opfamily_strategy(expr_op, index->opfamily[i])
- != op_strategy)
- break;
-
- /* Does collation match? */
- if (!IndexCollMatchesExprColl(index->indexcollations[i],
- lfirst_oid(collids_cell)))
- break;
+ /* Add column number to returned list */
+ *indexcolnos = lappend_int(*indexcolnos, i);
/* Add opfamily and datatypes to lists */
get_op_opfamily_properties(expr_op, index->opfamily[i], false,
/* Return clause as-is if it's all usable as index quals */
if (matching_cols == list_length(clause->opnos))
- return rinfo;
+ return (Expr *) clause;
/*
* We have to generate a subset rowcompare (possibly just one OpExpr). The
matching_cols);
rc->rargs = list_truncate((List *) copyObject(clause->rargs),
matching_cols);
- return make_simple_restrictinfo((Expr *) rc);
+ return (Expr *) rc;
}
else
{
- Expr *opexpr;
-
- opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false,
- copyObject(linitial(clause->largs)),
- copyObject(linitial(clause->rargs)),
- InvalidOid,
- linitial_oid(clause->inputcollids));
- return make_simple_restrictinfo(opexpr);
+ return make_opclause(linitial_oid(new_ops), BOOLOID, false,
+ copyObject(linitial(clause->largs)),
+ copyObject(linitial(clause->rargs)),
+ InvalidOid,
+ linitial_oid(clause->inputcollids));
}
}
List *indexquals);
static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path,
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 *order_qual_clauses(PlannerInfo *root, List *clauses);
static void copy_path_costsize(Plan *dest, Path *src);
* 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
* 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 *
create_indexscan_plan(PlannerInfo *root,
bool indexonly)
{
Scan *scan_plan;
- List *indexquals = best_path->indexquals;
List *indexorderbys = best_path->indexorderbys;
Index baserelid = best_path->path.parent->relid;
Oid indexoid = best_path->indexinfo->indexoid;
List *qpqual;
+ List *indexquals;
List *stripped_indexquals;
List *fixed_indexquals;
List *fixed_indexorderbys;
Assert(baserelid > 0);
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
* executor as indexqualorig
/*
* 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.
*/
- 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
clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
*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)
{
Expr *pred = (Expr *) lfirst(l);
* Adjust indexqual clauses to the form the executor's indexqual
* 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.
* * Replace any outer-relation Var or PHV nodes with nestloop Params.
* (XXX eventually, that responsibility should go elsewhere?)
{
IndexOptInfo *index = index_path->indexinfo;
List *fixed_indexquals;
- ListCell *l;
+ ListCell *lc1;
+ int indexcol;
fixed_indexquals = NIL;
- foreach(l, indexquals)
- {
- RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
- Node *clause;
-
- Assert(IsA(rinfo, RestrictInfo));
+ /* clausegroups must correspond to index columns */
+ Assert(list_length(indexquals) <= index->ncolumns);
- /*
- * Replace any outer-relation variables with nestloop params.
- *
- * This also makes a copy of the clause, so it's safe to modify it
- * in-place below.
- */
- clause = replace_nestloop_params(root, (Node *) rinfo->clause);
+ indexcol = 0;
+ foreach(lc1, indexquals)
+ {
+ List *clausegroup = (List *) lfirst(lc1);
+ ListCell *lc2;
- if (IsA(clause, OpExpr))
+ foreach(lc2, clausegroup)
{
- OpExpr *op = (OpExpr *) clause;
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
+ Node *clause;
- if (list_length(op->args) != 2)
- elog(ERROR, "indexqual clause is not binary opclause");
+ Assert(IsA(rinfo, RestrictInfo));
/*
- * Check to see if the indexkey is on the right; if so, commute
- * the clause. The indexkey should be the side that refers to
- * (only) the base relation.
+ * Replace any outer-relation variables with nestloop params.
+ *
+ * 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))
- CommuteOpExpr(op);
+ clause = replace_nestloop_params(root, (Node *) rinfo->clause);
- /*
- * Now, determine which index attribute this is and change the
- * indexkey operand as needed.
- */
- linitial(op->args) = fix_indexqual_operand(linitial(op->args),
- index);
- }
- else if (IsA(clause, RowCompareExpr))
- {
- RowCompareExpr *rc = (RowCompareExpr *) clause;
- ListCell *lc;
+ if (IsA(clause, OpExpr))
+ {
+ OpExpr *op = (OpExpr *) clause;
- /*
- * Check to see if the indexkey is on the right; if so, commute
- * 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);
+ if (list_length(op->args) != 2)
+ elog(ERROR, "indexqual clause is not binary opclause");
- /*
- * For each column in the row comparison, determine which index
- * attribute this is and change the indexkey operand as needed.
- */
- foreach(lc, rc->largs)
+ /*
+ * Check to see if the indexkey is on the right; if so,
+ * commute the clause. The indexkey should be the side that
+ * refers to (only) the base relation.
+ */
+ 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),
- index);
+ RowCompareExpr *rc = (RowCompareExpr *) clause;
+ 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))
- {
- ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+ else if (IsA(clause, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
- /* Never need to commute... */
+ /* Never need to commute... */
- /*
- * Determine which index attribute this is and change the indexkey
- * operand as needed.
- */
- linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
- index);
- }
- else if (IsA(clause, NullTest))
- {
- NullTest *nt = (NullTest *) clause;
+ /* Replace the indexkey expression with an index Var. */
+ linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
+ index,
+ indexcol);
+ }
+ else if (IsA(clause, NullTest))
+ {
+ 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,
- index);
+ fixed_indexquals = lappend(fixed_indexquals, clause);
}
- else
- elog(ERROR, "unsupported indexqual type: %d",
- (int) nodeTag(clause));
- fixed_indexquals = lappend(fixed_indexquals, clause);
+ indexcol++;
}
return fixed_indexquals;
* machinery needs.
*
* 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
* bother to support any cases except simple OpExprs, since nothing else
* is allowed for ordering operators.
{
IndexOptInfo *index = index_path->indexinfo;
List *fixed_indexorderbys;
- ListCell *l;
+ ListCell *lc1;
fixed_indexorderbys = NIL;
- foreach(l, indexorderbys)
+ foreach(lc1, indexorderbys)
{
- Node *clause = (Node *) lfirst(l);
+ List *percollists = (List *) lfirst(lc1);
+ ListCell *lc2;
+ int indexcol;
- /*
- * Replace any outer-relation variables with nestloop params.
- *
- * This also makes a copy of the clause, so it's safe to modify it
- * in-place below.
- */
- clause = replace_nestloop_params(root, clause);
+ /* percollists must correspond to index columns */
+ Assert(list_length(percollists) <= index->ncolumns);
- if (IsA(clause, OpExpr))
+ indexcol = 0;
+ foreach(lc2, percollists)
{
- OpExpr *op = (OpExpr *) clause;
+ List *percollist = (List *) lfirst(lc2);
- if (list_length(op->args) != 2)
- elog(ERROR, "indexorderby clause is not binary opclause");
+ if (percollist != NIL)
+ {
+ Node *clause = (Node *) linitial(percollist);
- /*
- * Now, determine which index attribute this is and change the
- * indexkey operand as needed.
- */
- linitial(op->args) = fix_indexqual_operand(linitial(op->args),
- index);
- }
- else
- elog(ERROR, "unsupported indexorderby type: %d",
- (int) nodeTag(clause));
+ /* Should have only one clause per index column */
+ Assert(list_length(percollist) == 1);
+
+ /*
+ * Replace any outer-relation variables with nestloop params.
+ *
+ * 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))
+ {
+ 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;
*
* We represent index keys by Var nodes having varno == INDEX_VAR and varattno
* 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 *
-fix_indexqual_operand(Node *node, IndexOptInfo *index)
+fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
{
Var *result;
int pos;
if (IsA(node, RelabelType))
node = (Node *) ((RelabelType *) node)->arg;
- if (IsA(node, Var) &&
- ((Var *) node)->varno == index->rel->relid)
- {
- /* Try to match against simple index columns */
- int varatt = ((Var *) node)->varattno;
+ Assert(indexcol >= 0 && indexcol < index->ncolumns);
- 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++)
- {
- if (index->indexkeys[pos] == varatt)
- {
- result = (Var *) copyObject(node);
- result->varno = INDEX_VAR;
- result->varattno = pos + 1;
- return (Node *) result;
- }
- }
+ result = (Var *) copyObject(node);
+ result->varno = INDEX_VAR;
+ result->varattno = indexcol + 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);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
{
- Node *indexkey;
-
if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list");
- indexkey = (Node *) lfirst(indexpr_item);
- if (indexkey && IsA(indexkey, RelabelType))
- indexkey = (Node *) ((RelabelType *) indexkey)->arg;
- if (equal(node, indexkey))
+ if (pos == indexcol)
{
- /* Found a match */
- result = makeVar(INDEX_VAR, pos + 1,
- exprType(lfirst(indexpr_item)), -1,
- exprCollation(lfirst(indexpr_item)),
- 0);
- return (Node *) result;
+ Node *indexkey;
+
+ indexkey = (Node *) lfirst(indexpr_item);
+ if (indexkey && IsA(indexkey, RelabelType))
+ indexkey = (Node *) ((RelabelType *) indexkey)->arg;
+ 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);
}
}
/* Ooops... */
- elog(ERROR, "node is not an index attribute");
+ elog(ERROR, "index key does not match expected index column");
return NULL; /* keep compiler quiet */
}
* 'index' is a usable index.
* 'clause_groups' is a list of lists of RestrictInfo nodes
* to be used as index qual conditions in the scan.
- * 'indexorderbys' is a list of bare expressions (no RestrictInfos)
- * to be used as index ordering operators in the scan.
+ * 'indexorderbys' is a list of lists of lists of bare expressions (not
+ * RestrictInfos) to be used as index ordering operators.
* 'pathkeys' describes the ordering of the path.
* 'indexscandir' is ForwardScanDirection or BackwardScanDirection
* for an ordered index, or NoMovementScanDirection for
* being used in an inner indexscan need not be checked again at the join.
*
* "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().
*
* Note that we are *not* checking for local redundancies within the given
List *selectivityQuals;
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
* given indexquals to produce a more accurate idea of the index
if (!predicate_implied_by(oneQual, indexQuals))
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);
}
else
bool found_saop;
bool found_is_null_op;
double num_sa_scans;
- ListCell *l;
+ ListCell *lc1;
/*
* For a btree scan, only leading '=' quals plus inequality quals for the
* 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
* for estimating numIndexTuples. So we must examine the given indexQuals
- * to find out which ones count as boundary quals. We rely on the
- * knowledge that they are given in index column order.
+ * to find out which ones count as boundary quals.
*
* For a RowCompareExpr, we consider only the first column, just as
* rowcomparesel() does.
* considered to act the same as it normally does.
*/
indexBoundQuals = NIL;
- indexcol = 0;
eqQualHere = false;
found_saop = false;
found_is_null_op = false;
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);
- Expr *clause;
- Node *leftop,
- *rightop;
- Oid clause_op;
- int op_strategy;
- bool is_null_op = false;
+ List *clausegroup = (List *) lfirst(lc1);
+ ListCell *lc2;
- 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;
+ eqQualHere = false;
- leftop = (Node *) linitial(rc->largs);
- rightop = (Node *) linitial(rc->rargs);
- clause_op = linitial_oid(rc->opnos);
- }
- else if (IsA(clause, ScalarArrayOpExpr))
+ foreach(lc2, clausegroup)
{
- 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);
- rightop = (Node *) lsecond(saop->args);
- clause_op = saop->opno;
- found_saop = true;
- }
- else if (IsA(clause, NullTest))
- {
- NullTest *nt = (NullTest *) clause;
+ leftop = (Node *) linitial(rc->largs);
+ rightop = (Node *) linitial(rc->rargs);
+ clause_op = linitial_oid(rc->opnos);
+ }
+ else if (IsA(clause, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
- leftop = (Node *) nt->arg;
- rightop = NULL;
- clause_op = InvalidOid;
- if (nt->nulltesttype == IS_NULL)
+ leftop = (Node *) linitial(saop->args);
+ rightop = (Node *) lsecond(saop->args);
+ clause_op = saop->opno;
+ found_saop = true;
+ }
+ else if (IsA(clause, NullTest))
{
- found_is_null_op = true;
- is_null_op = true;
+ NullTest *nt = (NullTest *) clause;
+
+ leftop = (Node *) nt->arg;
+ rightop = NULL;
+ clause_op = InvalidOid;
+ if (nt->nulltesttype == IS_NULL)
+ {
+ found_is_null_op = true;
+ is_null_op = true;
+ }
}
- }
- else
- {
- elog(ERROR, "unsupported indexqual type: %d",
- (int) nodeTag(clause));
- 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;
+ else
+ {
+ elog(ERROR, "unsupported indexqual type: %d",
+ (int) nodeTag(clause));
+ 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))
+ else
{
+ Assert(match_index_to_operand(rightop, indexcol, index));
/* Must flip operator to get the opfamily member */
clause_op = get_commutator(clause_op);
}
- else
+
+ /* check for equality operator */
+ if (OidIsValid(clause_op))
{
- /* No quals for new indexcol, so we are done */
- break;
- }
- }
- /* check for equality operator */
- if (OidIsValid(clause_op))
- {
- op_strategy = get_op_opfamily_strategy(clause_op,
+ op_strategy = get_op_opfamily_strategy(clause_op,
index->opfamily[indexcol]);
- Assert(op_strategy != 0); /* not a member of opfamily?? */
- if (op_strategy == BTEqualStrategyNumber)
+ Assert(op_strategy != 0); /* not a member of opfamily?? */
+ if (op_strategy == BTEqualStrategyNumber)
+ eqQualHere = true;
+ }
+ else if (is_null_op)
+ {
+ /* IS NULL is like = for selectivity determination */
eqQualHere = true;
- }
- else if (is_null_op)
- {
- /* IS NULL is like = for purposes of selectivity determination */
- eqQualHere = true;
- }
- /* 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));
+ }
+ /* 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)
- num_sa_scans *= alength;
+ if (alength > 1)
+ 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++;
}
/*
* NullTest invalidates that theory, even though it sets eqQualHere.
*/
if (index->unique &&
- indexcol == index->ncolumns - 1 &&
+ indexcol == index->ncolumns &&
eqQualHere &&
!found_saop &&
!found_is_null_op)
Relation indexRel;
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
*/
if (!predicate_implied_by(oneQual, indexQuals))
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);
}
else
* AND semantics across the list. Each clause is a RestrictInfo node from
* the query's WHERE or JOIN conditions.
*
- * 'indexquals' has the same structure as 'indexclauses', but it contains
- * the actual indexqual conditions that can be used with the index.
- * In simple cases this is identical to 'indexclauses', but when special
- * indexable operators appear in 'indexclauses', they are replaced by the
- * derived indexscannable conditions in 'indexquals'.
- *
- * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
- * Note that these are not RestrictInfos, just bare expressions, since they
- * generally won't yield booleans. The list will match the path's pathkeys.
- * Also, unlike the case for quals, it's guaranteed that each expression has
- * the index key on the left side of the operator.
+ * 'indexquals' is a list of sub-lists of the actual index qual conditions
+ * that can be used with the index. There is one possibly-empty sub-list
+ * for each index column (but empty sub-lists for trailing columns can be
+ * omitted). The qual conditions are RestrictInfos, and in simple cases
+ * are the same RestrictInfos that appear in the flat indexclauses list.
+ * But when special indexable operators appear in 'indexclauses', they are
+ * replaced by their derived indexscannable conditions in 'indexquals'.
+ * Note that an entirely empty indexquals list denotes a full-index scan.
+ *
+ * 'indexorderbys', if not NIL, is a list of lists of lists of ORDER BY
+ * expressions that have been found to be usable as ordering operators for an
+ * 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,
* some of the index conditions are join rather than restriction clauses).
List *clausegroups);
extern void check_partial_indexes(PlannerInfo *root, RelOptInfo *rel);
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
RESET enable_indexscan;
RESET enable_bitmapscan;
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)
+
default_tbl | f
defaultexpr_tbl | f
dept | f
+ dupindexcols | t
e_star | f
emp | f
equipment_r | f
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
-(153 rows)
+(154 rows)
--
-- another sanity check: every system catalog that has OIDs should have
default_tbl
defaultexpr_tbl
dept
+ dupindexcols
e_star
emp
equipment_r
toyemp
varchar_tbl
xacttest
-(107 rows)
+(108 rows)
SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
name
RESET enable_bitmapscan;
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';