diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index db663203da..7bc1e6e2ba 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -209,8 +209,10 @@ cost_seqscan(Path *path, PlannerInfo *root, * 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 @@ -221,8 +223,8 @@ cost_seqscan(Path *path, PlannerInfo *root, * 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 diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index fa6749a2f9..26d7c1c233 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -1148,7 +1148,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) * 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 @@ -1171,8 +1172,11 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) * 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, @@ -1182,6 +1186,7 @@ group_clauses_by_indexkey(IndexOptInfo *index, bool *found_clause) { List *clausegroup_list = NIL; + List *used_clauses = NIL; bool found_outer_clause = false; int indexcol; @@ -1201,13 +1206,16 @@ group_clauses_by_indexkey(IndexOptInfo *index, 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; @@ -1220,13 +1228,16 @@ group_clauses_by_indexkey(IndexOptInfo *index, 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; } } @@ -1240,6 +1251,8 @@ group_clauses_by_indexkey(IndexOptInfo *index, clausegroup_list = lappend(clausegroup_list, clausegroup); } + list_free(used_clauses); + if (!*found_clause && !found_outer_clause) return NIL; /* no indexable clauses anywhere */ @@ -1293,7 +1306,7 @@ group_clauses_by_indexkey(IndexOptInfo *index, * 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 @@ -1553,13 +1566,15 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, * 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 */ @@ -1606,7 +1621,17 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys) 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; } @@ -1620,7 +1645,7 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys) return NIL; } - return orderbyexprs; /* success! */ + return orderbylists; /* success! */ } /* @@ -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 * 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) @@ -2449,6 +2476,38 @@ 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 ---- @@ -2577,8 +2636,8 @@ match_index_to_operand(Node *operand, * 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" @@ -2796,22 +2855,20 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation, /* * 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; @@ -2827,6 +2884,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) List *clausegroup = (List *) lfirst(lc); Oid curFamily = index->opfamily[indexcol]; Oid curCollation = index->indexcollations[indexcol]; + List *newgroup = NIL; ListCell *lc2; foreach(lc2, clausegroup) @@ -2844,8 +2902,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) index); if (boolqual) { - resultquals = lappend(resultquals, - make_simple_restrictinfo(boolqual)); + newgroup = lappend(newgroup, + make_simple_restrictinfo(boolqual)); continue; } } @@ -2856,38 +2914,40 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) */ 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; } /* @@ -3054,6 +3114,41 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation) * 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 @@ -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 * 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; @@ -3094,6 +3199,8 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, 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); @@ -3101,6 +3208,10 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, &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); @@ -3147,28 +3258,22 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, 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, @@ -3189,7 +3294,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, /* 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 @@ -3260,18 +3365,15 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, 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)); } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 8138b0118b..04024cc493 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -87,7 +87,7 @@ static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, 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); @@ -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 * 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, @@ -1086,11 +1082,11 @@ 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; @@ -1100,6 +1096,13 @@ create_indexscan_plan(PlannerInfo *root, 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 @@ -1108,14 +1111,23 @@ create_indexscan_plan(PlannerInfo *root, /* * 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 @@ -1494,7 +1506,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, 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); @@ -2472,7 +2484,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) * 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?) @@ -2492,96 +2505,129 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, { IndexOptInfo *index = index_path->indexinfo; List *fixed_indexquals; - ListCell *l; + ListCell *lc1; + int indexcol; fixed_indexquals = NIL; - 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); - Node *clause; + List *clausegroup = (List *) lfirst(lc1); + ListCell *lc2; - Assert(IsA(rinfo, RestrictInfo)); - - /* - * 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); - - 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; - - /* - * 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); - - /* - * For each column in the row comparison, determine which index - * attribute this is and change the indexkey operand as needed. - */ - foreach(lc, rc->largs) + if (IsA(clause, OpExpr)) { - lfirst(lc) = fix_indexqual_operand(lfirst(lc), - index); + OpExpr *op = (OpExpr *) clause; + + if (list_length(op->args) != 2) + elog(ERROR, "indexqual clause is not binary opclause"); + + /* + * 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)) + { + 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; + + /* Never need to commute... */ + + /* 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)); + + fixed_indexquals = lappend(fixed_indexquals, clause); } - else if (IsA(clause, ScalarArrayOpExpr)) - { - ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; - /* 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; - - nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg, - index); - } - else - elog(ERROR, "unsupported indexqual type: %d", - (int) nodeTag(clause)); - - fixed_indexquals = lappend(fixed_indexquals, clause); + indexcol++; } return fixed_indexquals; @@ -2593,7 +2639,7 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path, * 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. @@ -2604,41 +2650,62 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path, { 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); + /* 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); + } + + indexcol++; } - else - elog(ERROR, "unsupported indexorderby type: %d", - (int) nodeTag(clause)); - - fixed_indexorderbys = lappend(fixed_indexorderbys, clause); } return fixed_indexorderbys; @@ -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 * 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; @@ -2664,55 +2734,56 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index) 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 */ } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 1e7aac95ef..9e99c9d9d7 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -412,8 +412,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel) * '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 diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 63ff431900..8e3d4bb845 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -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. * * "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 diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index bb411f9ad1..3e6cabf7e7 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5991,6 +5991,14 @@ genericcostestimate(PlannerInfo *root, 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 @@ -6022,7 +6030,7 @@ genericcostestimate(PlannerInfo *root, 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 @@ -6250,7 +6258,7 @@ btcostestimate(PG_FUNCTION_ARGS) 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 @@ -6259,8 +6267,7 @@ btcostestimate(PG_FUNCTION_ARGS) * 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. @@ -6270,120 +6277,119 @@ btcostestimate(PG_FUNCTION_ARGS) * 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; - 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 *) nt->arg; - rightop = NULL; - clause_op = InvalidOid; - if (nt->nulltesttype == IS_NULL) + Assert(IsA(rinfo, RestrictInfo)); + clause = rinfo->clause; + if (IsA(clause, OpExpr)) { - found_is_null_op = true; - is_null_op = true; + leftop = get_leftop(clause); + rightop = get_rightop(clause); + clause_op = ((OpExpr *) clause)->opno; } - } - 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 if (IsA(clause, RowCompareExpr)) + { + RowCompareExpr *rc = (RowCompareExpr *) 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 *) 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 *) 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)) + else { + Assert(match_index_to_operand(rightop, indexcol, index)); /* Must flip operator to get the opfamily member */ clause_op = get_commutator(clause_op); } - else - { - /* 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, - index->opfamily[indexcol]); - 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 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)); - if (alength > 1) - num_sa_scans *= alength; + /* check for equality operator */ + if (OidIsValid(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) + eqQualHere = true; + } + else if (is_null_op) + { + /* IS NULL is like = for 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)); + + 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++; } /* @@ -6393,7 +6399,7 @@ btcostestimate(PG_FUNCTION_ARGS) * 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) @@ -6924,6 +6930,14 @@ gincostestimate(PG_FUNCTION_ARGS) 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 */ @@ -6980,7 +6994,7 @@ gincostestimate(PG_FUNCTION_ARGS) 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 diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 74c060b9b6..b137142f3e 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -659,18 +659,25 @@ typedef struct Path * 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'. + * '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 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. + * '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). diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index c62f4a8122..b0075d7865 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -61,6 +61,12 @@ extern List *expand_indexqual_conditions(IndexOptInfo *index, 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 diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 36198b8edd..18457e0ad9 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2459,3 +2459,27 @@ RESET enable_seqscan; 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) + diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 9cae9d8bf1..05ac11cfb0 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -39,6 +39,7 @@ SELECT relname, relhasindex default_tbl | f defaultexpr_tbl | f dept | f + dupindexcols | t e_star | f emp | f equipment_r | f @@ -164,7 +165,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(153 rows) +(154 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source index b57c5546de..03aa10df08 100644 --- a/src/test/regress/output/misc.source +++ b/src/test/regress/output/misc.source @@ -610,6 +610,7 @@ SELECT user_relns() AS user_relns default_tbl defaultexpr_tbl dept + dupindexcols e_star emp equipment_r @@ -685,7 +686,7 @@ SELECT user_relns() AS user_relns toyemp varchar_tbl xacttest -(107 rows) +(108 rows) SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); name diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index babde51d2c..8c60cb6145 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -804,3 +804,18 @@ 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'; +SELECT count(*) FROM dupindexcols + WHERE f1 > 'LX' and id < 1000 and f1 ~<~ 'YX';