diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 9c22d31150..1ee3b93b1e 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -45,14 +45,6 @@ #define IndexCollMatchesExprColl(idxcollation, exprcollation) \ ((idxcollation) == InvalidOid || (idxcollation) == (exprcollation)) -/* Whether to use ScalarArrayOpExpr to build index qualifications */ -typedef enum -{ - SAOP_PER_AM, /* Use ScalarArrayOpExpr if amsearcharray */ - SAOP_ALLOW, /* Use ScalarArrayOpExpr for all indexes */ - SAOP_REQUIRE /* Require ScalarArrayOpExpr to be used */ -} SaOpControl; - /* Whether we are looking for plain indexscan, bitmap scan, or either */ typedef enum { @@ -118,7 +110,9 @@ static void get_index_paths(PlannerInfo *root, RelOptInfo *rel, static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel, IndexOptInfo *index, IndexClauseSet *clauses, bool useful_predicate, - SaOpControl saop_control, ScanTypeControl scantype); + ScanTypeControl scantype, + bool *skip_nonnative_saop, + bool *skip_lower_saop); static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *other_clauses); static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, @@ -726,6 +720,8 @@ bms_equal_any(Relids relids, List *relids_list) * index AM supports them natively, we should just include them in simple * index paths. If not, we should exclude them while building simple index * paths, and then make a separate attempt to include them in bitmap paths. + * Furthermore, we should consider excluding lower-order ScalarArrayOpExpr + * quals so as to create ordered paths. */ static void get_index_paths(PlannerInfo *root, RelOptInfo *rel, @@ -733,16 +729,38 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel, List **bitindexpaths) { List *indexpaths; + bool skip_nonnative_saop = false; + bool skip_lower_saop = false; ListCell *lc; /* * Build simple index paths using the clauses. Allow ScalarArrayOpExpr - * clauses only if the index AM supports them natively. + * clauses only if the index AM supports them natively, and skip any such + * clauses for index columns after the first (so that we produce ordered + * paths if possible). */ indexpaths = build_index_paths(root, rel, index, clauses, index->predOK, - SAOP_PER_AM, ST_ANYSCAN); + ST_ANYSCAN, + &skip_nonnative_saop, + &skip_lower_saop); + + /* + * If we skipped any lower-order ScalarArrayOpExprs on an index with an AM + * that supports them, then try again including those clauses. This will + * produce paths with more selectivity but no ordering. + */ + if (skip_lower_saop) + { + indexpaths = list_concat(indexpaths, + build_index_paths(root, rel, + index, clauses, + index->predOK, + ST_ANYSCAN, + &skip_nonnative_saop, + NULL)); + } /* * Submit all the ones that can form plain IndexScan plans to add_path. (A @@ -770,16 +788,18 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel, } /* - * If the index doesn't handle ScalarArrayOpExpr clauses natively, check - * to see if there are any such clauses, and if so generate bitmap scan - * paths relying on executor-managed ScalarArrayOpExpr. + * If there were ScalarArrayOpExpr clauses that the index can't handle + * natively, generate bitmap scan paths relying on executor-managed + * ScalarArrayOpExpr. */ - if (!index->amsearcharray) + if (skip_nonnative_saop) { indexpaths = build_index_paths(root, rel, index, clauses, false, - SAOP_REQUIRE, ST_BITMAPSCAN); + ST_BITMAPSCAN, + NULL, + NULL); *bitindexpaths = list_concat(*bitindexpaths, indexpaths); } } @@ -802,26 +822,36 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel, * Note that this routine should never be called at all if the index has an * unprovable predicate. * - * saop_control indicates whether ScalarArrayOpExpr clauses can be used. - * When it's SAOP_REQUIRE, index paths are created only if we found at least - * one ScalarArrayOpExpr clause. - * * scantype indicates whether we want to create plain indexscans, bitmap * indexscans, or both. When it's ST_BITMAPSCAN, we will not consider * index ordering while deciding if a Path is worth generating. * + * If skip_nonnative_saop is non-NULL, we ignore ScalarArrayOpExpr clauses + * unless the index AM supports them directly, and we set *skip_nonnative_saop + * to TRUE if we found any such clauses (caller must initialize the variable + * to FALSE). If it's NULL, we do not ignore ScalarArrayOpExpr clauses. + * + * If skip_lower_saop is non-NULL, we ignore ScalarArrayOpExpr clauses for + * non-first index columns, and we set *skip_lower_saop to TRUE if we found + * any such clauses (caller must initialize the variable to FALSE). If it's + * NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will + * result in considering the scan's output to be unordered. + * * 'rel' is the index's heap relation * 'index' is the index for which we want to generate paths * 'clauses' is the collection of indexable clauses (RestrictInfo nodes) * 'useful_predicate' indicates whether the index has a useful predicate - * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used * 'scantype' indicates whether we need plain or bitmap scan support + * 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't + * 'skip_lower_saop' indicates whether to accept non-first-column SAOP */ static List * build_index_paths(PlannerInfo *root, RelOptInfo *rel, IndexOptInfo *index, IndexClauseSet *clauses, bool useful_predicate, - SaOpControl saop_control, ScanTypeControl scantype) + ScanTypeControl scantype, + bool *skip_nonnative_saop, + bool *skip_lower_saop) { List *result = NIL; IndexPath *ipath; @@ -833,7 +863,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, List *orderbyclausecols; List *index_pathkeys; List *useful_pathkeys; - bool found_clause; bool found_lower_saop_clause; bool pathkeys_possibly_useful; bool index_is_ordered; @@ -868,11 +897,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, * (This order is depended on by btree and possibly other places.) The * lists can be empty, if the index AM allows that. * - * found_clause is set true only if there's at least one index clause; and - * if saop_control is SAOP_REQUIRE, it has to be a ScalarArrayOpExpr - * clause. - * - * found_lower_saop_clause is set true if there's a ScalarArrayOpExpr + * found_lower_saop_clause is set true if we accept a ScalarArrayOpExpr * index clause for a non-first index column. This prevents us from * assuming that the scan result is ordered. (Actually, the result is * still ordered if there are equality constraints for all earlier @@ -885,7 +910,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, */ index_clauses = NIL; clause_columns = NIL; - found_clause = false; found_lower_saop_clause = false; outer_relids = bms_copy(rel->lateral_relids); for (indexcol = 0; indexcol < index->ncolumns; indexcol++) @@ -898,17 +922,27 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, if (IsA(rinfo->clause, ScalarArrayOpExpr)) { - /* Ignore if not supported by index */ - if (saop_control == SAOP_PER_AM && !index->amsearcharray) - continue; - found_clause = true; + if (!index->amsearcharray) + { + if (skip_nonnative_saop) + { + /* Ignore because not supported by index */ + *skip_nonnative_saop = true; + continue; + } + /* Caller had better intend this only for bitmap scan */ + Assert(scantype == ST_BITMAPSCAN); + } if (indexcol > 0) + { + if (skip_lower_saop) + { + /* Caller doesn't want to lose index ordering */ + *skip_lower_saop = true; + continue; + } found_lower_saop_clause = true; - } - else - { - if (saop_control != SAOP_REQUIRE) - found_clause = true; + } } index_clauses = lappend(index_clauses, rinfo); clause_columns = lappend_int(clause_columns, indexcol); @@ -988,7 +1022,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, * later merging or final output ordering, OR the index has a useful * predicate, OR an index-only scan is possible. */ - if (found_clause || useful_pathkeys != NIL || useful_predicate || + if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate || index_only_scan) { ipath = create_index_path(root, index, @@ -1137,7 +1171,9 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, indexpaths = build_index_paths(root, rel, index, &clauseset, useful_predicate, - SAOP_ALLOW, ST_BITMAPSCAN); + ST_BITMAPSCAN, + NULL, + NULL); result = list_concat(result, indexpaths); } diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 8326e94250..d903c4bc26 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2754,6 +2754,27 @@ ORDER BY unique1; 42 (3 rows) +explain (costs off) +SELECT thousand, tenthous FROM tenk1 +WHERE thousand < 2 AND tenthous IN (1001,3000) +ORDER BY thousand; + QUERY PLAN +------------------------------------------------------- + Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (thousand < 2) + Filter: (tenthous = ANY ('{1001,3000}'::integer[])) +(3 rows) + +SELECT thousand, tenthous FROM tenk1 +WHERE thousand < 2 AND tenthous IN (1001,3000) +ORDER BY thousand; + thousand | tenthous +----------+---------- + 0 | 3000 + 1 | 1001 +(2 rows) + +SET enable_indexonlyscan = OFF; explain (costs off) SELECT thousand, tenthous FROM tenk1 WHERE thousand < 2 AND tenthous IN (1001,3000) @@ -2762,7 +2783,7 @@ ORDER BY thousand; -------------------------------------------------------------------------------------- Sort Sort Key: thousand - -> Index Only Scan using tenk1_thous_tenthous on tenk1 + -> Index Scan using tenk1_thous_tenthous on tenk1 Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[]))) (4 rows) @@ -2775,6 +2796,7 @@ ORDER BY thousand; 1 | 1001 (2 rows) +RESET enable_indexscan; -- -- Check elimination of constant-NULL subexpressions -- diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index d4d24ef82b..989fc97eee 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -932,6 +932,19 @@ SELECT thousand, tenthous FROM tenk1 WHERE thousand < 2 AND tenthous IN (1001,3000) ORDER BY thousand; +SET enable_indexonlyscan = OFF; + +explain (costs off) +SELECT thousand, tenthous FROM tenk1 +WHERE thousand < 2 AND tenthous IN (1001,3000) +ORDER BY thousand; + +SELECT thousand, tenthous FROM tenk1 +WHERE thousand < 2 AND tenthous IN (1001,3000) +ORDER BY thousand; + +RESET enable_indexscan; + -- -- Check elimination of constant-NULL subexpressions --