diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 6b2b9e3..71636ae 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amstrategies = BLOOM_NSTRATEGIES; amroutine->amsupport = BLOOM_NPROC; amroutine->amcanorder = false; - amroutine->amcanorderbyop = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; @@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->ammatchorderby = NULL; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index e95fbbc..ffb6de0 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -86,7 +86,6 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amstrategies = 0; amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM; amroutine->amcanorder = false; - amroutine->amcanorderbyop = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; @@ -120,6 +119,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->ammatchorderby = NULL; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 0a32182..c142d09 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amstrategies = 0; amroutine->amsupport = GINNProcs; amroutine->amcanorder = false; - amroutine->amcanorderbyop = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; @@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->ammatchorderby = NULL; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 8a42eff..f60bb45 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -18,6 +18,7 @@ #include "access/gistscan.h" #include "catalog/pg_collation.h" #include "miscadmin.h" +#include "optimizer/paths.h" #include "storage/lmgr.h" #include "storage/predicate.h" #include "nodes/execnodes.h" @@ -63,7 +64,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amstrategies = 0; amroutine->amsupport = GISTNProcs; amroutine->amcanorder = false; - amroutine->amcanorderbyop = true; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; @@ -97,6 +97,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->ammatchorderby = match_orderbyop_pathkeys; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 0002df3..39f7f45 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amstrategies = HTMaxStrategyNumber; amroutine->amsupport = HASHNProcs; amroutine->amcanorder = false; - amroutine->amcanorderbyop = false; amroutine->amcanbackward = true; amroutine->amcanunique = false; amroutine->amcanmulticol = false; @@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->ammatchorderby = NULL; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index e8725fb..3e47c37 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amstrategies = BTMaxStrategyNumber; amroutine->amsupport = BTNProcs; amroutine->amcanorder = true; - amroutine->amcanorderbyop = false; amroutine->amcanbackward = true; amroutine->amcanunique = true; amroutine->amcanmulticol = true; @@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = btestimateparallelscan; amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->ammatchorderby = NULL; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 9919e6f..a843650 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -32,10 +32,6 @@ #include "utils/lsyscache.h" #include "utils/syscache.h" -extern Expr *spgcanorderbyop(IndexOptInfo *index, - PathKey *pathkey, int pathkeyno, - Expr *orderby_clause, int *indexcol_p); - /* * SP-GiST handler function: return IndexAmRoutine with access method parameters * and callbacks. @@ -48,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amstrategies = 0; amroutine->amsupport = SPGISTNProc; amroutine->amcanorder = false; - amroutine->amcanorderbyop = true; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = false; @@ -82,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->ammatchorderby = match_orderbyop_pathkeys; PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 3b5c90e..6c763ef 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -1083,7 +1083,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid) */ IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false); - if (!amroutine->amcanorderbyop) + if (!amroutine->ammatchorderby) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("access method \"%s\" does not support ordering operators", diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index 6285a21..b7262e3 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -199,7 +199,7 @@ IndexNextWithReorder(IndexScanState *node) * with just Asserting here because the system will not try to run the * plan backwards if ExecSupportsBackwardScan() says it won't work. * Currently, that is guaranteed because no index AMs support both - * amcanorderbyop and amcanbackward; if any ever do, + * ammatchorderby and amcanbackward; if any ever do, * ExecSupportsBackwardScan() will need to consider indexorderbys * explicitly. */ @@ -1145,7 +1145,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags) * 5. NullTest ("indexkey IS NULL/IS NOT NULL"). We just fill in the * ScanKey properly. * - * This code is also used to prepare ORDER BY expressions for amcanorderbyop + * This code is also used to prepare ORDER BY expressions for ammatchorderby * indexes. The behavior is exactly the same, except that we have to look up * the operator differently. Note that only cases 1 and 2 are currently * possible for ORDER BY. diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index f295558..e043664 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -17,6 +17,7 @@ #include +#include "access/amapi.h" #include "access/stratnum.h" #include "access/sysattr.h" #include "catalog/pg_am.h" @@ -154,6 +155,10 @@ static void match_clause_to_index(IndexOptInfo *index, static bool match_clause_to_indexcol(IndexOptInfo *index, int indexcol, RestrictInfo *rinfo); +static Expr *match_clause_to_ordering_op(IndexOptInfo *index, + int indexcol, + Expr *clause, + Oid pk_opfamily); static bool is_indexable_operator(Oid expr_op, Oid opfamily, bool indexkey_on_left); static bool match_rowcompare_to_indexcol(IndexOptInfo *index, @@ -164,8 +169,6 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index, static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, List **orderby_clauses_p, List **clause_columns_p); -static Expr *match_clause_to_ordering_op(IndexOptInfo *index, - int indexcol, Expr *clause, Oid pk_opfamily); static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *ec, EquivalenceMember *em, void *arg); @@ -998,7 +1001,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, orderbyclauses = NIL; orderbyclausecols = NIL; } - else if (index->amcanorderbyop && pathkeys_possibly_useful) + else if (index->ammatchorderby && pathkeys_possibly_useful) { /* see if we can generate ordering operators for query_pathkeys */ match_pathkeys_to_index(index, root->query_pathkeys, @@ -2545,6 +2548,99 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, return false; } +Expr * +match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p) +{ + ListCell *lc; + + /* Pathkey must request default sort order for the target opfamily */ + if (pathkey->pk_strategy != BTLessStrategyNumber || + pathkey->pk_nulls_first) + return NULL; + + /* If eclass is volatile, no hope of using an indexscan */ + if (pathkey->pk_eclass->ec_has_volatile) + return NULL; + + /* + * Try to match eclass member expression(s) to index. Note that child + * EC members are considered, but only when they belong to the target + * relation. (Unlike regular members, the same expression could be a + * child member of more than one EC. Therefore, the same index could + * be considered to match more than one pathkey list, which is OK + * here. See also get_eclass_for_sort_expr.) + */ + foreach(lc, pathkey->pk_eclass->ec_members) + { + EquivalenceMember *member = castNode(EquivalenceMember, lfirst(lc)); + int indexcol; + int indexcol_min; + int indexcol_max; + + /* No possibility of match if it references other relations */ + if (!bms_equal(member->em_relids, index->rel->relids)) + continue; + + /* If *indexcol_p is non-negative then try to match only to it */ + if (*indexcol_p >= 0) + { + indexcol_min = *indexcol_p; + indexcol_max = *indexcol_p + 1; + } + else /* try to match all columns */ + { + indexcol_min = 0; + indexcol_max = index->ncolumns; + } + + /* + * We allow any column of the GiST index to match each pathkey; + * they don't have to match left-to-right as you might expect. + */ + for (indexcol = indexcol_min; indexcol < indexcol_max; indexcol++) + { + Expr *expr = match_clause_to_ordering_op(index, + indexcol, + member->em_expr, + pathkey->pk_opfamily); + if (expr) + { + *indexcol_p = indexcol; + return expr; /* don't want to look at remaining members */ + } + } + } + + return NULL; +} + +bool +match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys, + List **orderby_clauses_p, List **clause_columns_p) +{ + ListCell *lc; + + foreach(lc, pathkeys) + { + PathKey *pathkey = castNode(PathKey, lfirst(lc)); + Expr *expr; + int indexcol = -1; /* match all index columns */ + + expr = match_orderbyop_pathkey(index, pathkey, &indexcol); + + /* + * Note: for any failure to match, we just return NIL immediately. + * There is no value in matching just some of the pathkeys. + */ + if (!expr) + return false; + + *orderby_clauses_p = lappend(*orderby_clauses_p, expr); + *clause_columns_p = lappend_int(*clause_columns_p, indexcol); + } + + return true; /* success */ +} /**************************************************************************** * ---- ROUTINES TO CHECK ORDERING OPERATORS ---- @@ -2570,86 +2666,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, { List *orderby_clauses = NIL; List *clause_columns = NIL; - ListCell *lc1; + ammatchorderby_function ammatchorderby = + (ammatchorderby_function) index->ammatchorderby; - *orderby_clauses_p = NIL; /* set default results */ - *clause_columns_p = NIL; - - /* Only indexes with the amcanorderbyop property are interesting here */ - if (!index->amcanorderbyop) - return; - - foreach(lc1, pathkeys) + /* Only indexes with the ammatchorderby function are interesting here */ + if (ammatchorderby && + ammatchorderby(index, pathkeys, &orderby_clauses, &clause_columns)) { - PathKey *pathkey = (PathKey *) lfirst(lc1); - bool found = false; - ListCell *lc2; - - /* - * Note: for any failure to match, we just return NIL immediately. - * There is no value in matching just some of the pathkeys. - */ - - /* Pathkey must request default sort order for the target opfamily */ - if (pathkey->pk_strategy != BTLessStrategyNumber || - pathkey->pk_nulls_first) - return; + Assert(list_length(pathkeys) == list_length(orderby_clauses)); + Assert(list_length(pathkeys) == list_length(clause_columns)); - /* If eclass is volatile, no hope of using an indexscan */ - if (pathkey->pk_eclass->ec_has_volatile) - return; - - /* - * Try to match eclass member expression(s) to index. Note that child - * EC members are considered, but only when they belong to the target - * relation. (Unlike regular members, the same expression could be a - * child member of more than one EC. Therefore, the same index could - * be considered to match more than one pathkey list, which is OK - * here. See also get_eclass_for_sort_expr.) - */ - foreach(lc2, pathkey->pk_eclass->ec_members) - { - EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2); - int indexcol; - - /* No possibility of match if it references other relations */ - if (!bms_equal(member->em_relids, index->rel->relids)) - continue; - - /* - * We allow any column of the index to match each pathkey; they - * don't have to match left-to-right as you might expect. This is - * correct for GiST, which is the sole existing AM supporting - * amcanorderbyop. We might need different logic in future for - * other implementations. - */ - for (indexcol = 0; indexcol < index->ncolumns; indexcol++) - { - Expr *expr; - - expr = match_clause_to_ordering_op(index, - indexcol, - member->em_expr, - pathkey->pk_opfamily); - if (expr) - { - orderby_clauses = lappend(orderby_clauses, expr); - clause_columns = lappend_int(clause_columns, indexcol); - found = true; - break; - } - } - - if (found) /* don't want to look at remaining members */ - break; - } - - if (!found) /* fail if no match for this pathkey */ - return; + *orderby_clauses_p = orderby_clauses; /* success! */ + *clause_columns_p = clause_columns; + } + else + { + *orderby_clauses_p = NIL; /* set default results */ + *clause_columns_p = NIL; } - - *orderby_clauses_p = orderby_clauses; /* success! */ - *clause_columns_p = clause_columns; } /* diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8369e3a..6c26d26 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -266,7 +266,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, /* We copy just the fields we need, not all of rd_amroutine */ amroutine = indexRelation->rd_amroutine; - info->amcanorderbyop = amroutine->amcanorderbyop; + info->ammatchorderby = amroutine->ammatchorderby; info->amoptionalkey = amroutine->amoptionalkey; info->amsearcharray = amroutine->amsearcharray; info->amsearchnulls = amroutine->amsearchnulls; diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c index dc04148..02879e3 100644 --- a/src/backend/utils/adt/amutils.c +++ b/src/backend/utils/adt/amutils.c @@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo, * a nonkey column, and null otherwise (meaning we don't * know). */ - if (!iskey || !routine->amcanorderbyop) + if (!iskey || !routine->ammatchorderby) { res = false; isnull = false; diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 14526a6..a293b38 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -21,6 +21,9 @@ */ struct PlannerInfo; struct IndexPath; +struct IndexOptInfo; +struct PathKey; +struct Expr; /* Likewise, this file shouldn't depend on execnodes.h. */ struct IndexInfo; @@ -140,6 +143,12 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* does AM support ORDER BY result of an operator on indexed column? */ +typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index, + List *pathkeys, + List **orderby_clauses_p, + List **clause_columns_p); + /* * Callback function signatures - for parallel index scans. */ @@ -170,8 +179,6 @@ typedef struct IndexAmRoutine uint16 amsupport; /* does AM support ORDER BY indexed column's value? */ bool amcanorder; - /* does AM support ORDER BY result of an operator on indexed column? */ - bool amcanorderbyop; /* does AM support backward scanning? */ bool amcanbackward; /* does AM support UNIQUE indexes? */ @@ -221,7 +228,8 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ - + ammatchorderby_function ammatchorderby; /* can be NULL */ + /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ aminitparallelscan_function aminitparallelscan; /* can be NULL */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 7c2abbd..5bbfc5a 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -395,7 +395,7 @@ typedef struct SampleScan * indexorderbyops is a list of the OIDs of the operators used to sort the * ORDER BY expressions. This is used together with indexorderbyorig to * recheck ordering at run time. (Note that indexorderby, indexorderbyorig, - * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.) + * and indexorderbyops are used for ammatchorderby cases, not amcanorder.) * * indexorderdir specifies the scan ordering, for indexscans on amcanorder * indexes (for other indexes it should be "don't care"). diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index adb4265..75ee8d6 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -805,7 +805,6 @@ typedef struct IndexOptInfo bool hypothetical; /* true if index doesn't really exist */ /* Remaining fields are copied from the index AM's API struct: */ - bool amcanorderbyop; /* does AM support order by operator result? */ bool amoptionalkey; /* can query omit key for the first column? */ bool amsearcharray; /* can AM handle ScalarArrayOpExpr quals? */ bool amsearchnulls; /* can AM search for NULL/NOT NULL entries? */ @@ -814,6 +813,11 @@ typedef struct IndexOptInfo bool amcanparallel; /* does AM support parallel scan? */ /* Rather than include amapi.h here, we declare amcostestimate like this */ void (*amcostestimate) (); /* AM's cost estimator */ + /* AM order-by match function */ + bool (*ammatchorderby) (struct IndexOptInfo *index, + List *pathkeys, + List **orderby_clauses_p, + List **clause_columns_p); } IndexOptInfo; /* @@ -1137,7 +1141,7 @@ typedef struct Path * (The order of multiple quals for the same index column is unspecified.) * * '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. + * been found to be usable as ordering operators for an ammatchorderby index. * The list must match the path's pathkeys, ie, one expression per pathkey * in the same order. These are not RestrictInfos, just bare expressions, * since they generally won't yield booleans. Also, unlike the case for diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index cafde30..21677eb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -87,6 +87,10 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause, int indexcol, List **indexcolnos, bool *var_on_left_p); +extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, + int *indexcol_p); +extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys, + List **orderby_clauses_p, List **clause_columns_p); /* * tidpath.h