From 119167ed2f0ddd18ef00a78d197780a67b492c15 Mon Sep 17 00:00:00 2001 From: Alexandre Felipe Date: Fri, 3 Apr 2026 17:34:30 +0100 Subject: [PATCH-15.3 2/8] Optimized reverse pathkeys Instead of calling build_index_pathkeys twice (once for forward and once for backward scan direction), compute pathkeys only for the forward scan and derive backward pathkeys using reverse_pathkeys(). This removes the ScanDirection parameter from build_index_pathkeys and adds two new helper functions: - make_reversed_pathkey: reverses a single pathkey's sort direction - reverse_pathkeys: reverses a list of pathkeys This gives measurable performance improvement in the planner benchmark. Additionally, it will reduce the per-index cost of the SLOPE analysis in the upcomming commits. --- src/backend/optimizer/path/indxpath.c | 28 +++++------ src/backend/optimizer/path/pathkeys.c | 68 +++++++++++++++++++++------ src/include/optimizer/paths.h | 5 +- 3 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 3f5d4fa3182..0cbf81932c7 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -912,17 +912,20 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, * many of them are actually useful for this query. This is not relevant * if we are only trying to build bitmap indexscans. */ + + orderbyclauses = NIL; + orderbyclausecols = NIL; + index_pathkeys = NIL; + useful_pathkeys = NIL; + pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN && has_useful_pathkeys(root, rel)); index_is_ordered = (index->sortopfamily != NULL); if (index_is_ordered && pathkeys_possibly_useful) { - index_pathkeys = build_index_pathkeys(root, index, - ForwardScanDirection); + index_pathkeys = build_index_pathkeys(root, index); useful_pathkeys = truncate_useless_pathkeys(root, rel, index_pathkeys); - orderbyclauses = NIL; - orderbyclausecols = NIL; } else if (index->amcanorderbyop && pathkeys_possibly_useful) { @@ -941,12 +944,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, useful_pathkeys = list_copy_head(root->query_pathkeys, list_length(orderbyclauses)); } - else - { - useful_pathkeys = NIL; - orderbyclauses = NIL; - orderbyclausecols = NIL; - } /* * 3. Check if an index-only scan is possible. If we're not building @@ -1008,14 +1005,15 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, } /* - * 5. If the index is ordered, a backwards scan might be interesting. + * 5. If the index is ordered, a backwards scan might be interesting. Only + * consider it if we have forward pathkeys to reverse. */ - if (index_is_ordered && pathkeys_possibly_useful) + if (index_pathkeys != NIL) { - index_pathkeys = build_index_pathkeys(root, index, - BackwardScanDirection); + List *backward_pathkeys = reverse_pathkeys(root, index_pathkeys); + useful_pathkeys = truncate_useless_pathkeys(root, rel, - index_pathkeys); + backward_pathkeys); if (useful_pathkeys != NIL) { ipath = create_index_path(root, index, diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index d6a305aedac..3610b6d9694 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -98,6 +98,54 @@ make_canonical_pathkey(PlannerInfo *root, return pk; } +/* + * make_reversed_pathkey + * Create a pathkey with reversed sort direction. + * + * This flips COMPARE_LT <-> COMPARE_GT and inverts nulls_first. + */ +PathKey * +make_reversed_pathkey(PlannerInfo *root, PathKey *pathkey) +{ + CompareType reversed_cmptype; + + if (pathkey->pk_cmptype == COMPARE_LT) + reversed_cmptype = COMPARE_GT; + else if (pathkey->pk_cmptype == COMPARE_GT) + reversed_cmptype = COMPARE_LT; + else + reversed_cmptype = pathkey->pk_cmptype; + + return make_canonical_pathkey(root, + pathkey->pk_eclass, + pathkey->pk_opfamily, + reversed_cmptype, + !pathkey->pk_nulls_first); +} + +/* + * reverse_pathkeys + * Create a list of pathkeys with reversed sort directions. + * + * This is useful for deriving backward index scan pathkeys from + * forward scan pathkeys without recomputing them from scratch. + */ +List * +reverse_pathkeys(PlannerInfo *root, List *pathkeys) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, pathkeys) + { + PathKey *pk = lfirst_node(PathKey, lc); + + result = lappend(result, make_reversed_pathkey(root, pk)); + } + + return result; +} + /* * append_pathkeys * Append all non-redundant PathKeys in 'source' onto 'target' and @@ -722,8 +770,8 @@ get_cheapest_parallel_safe_total_inner(List *paths) * scan using the given index. (Note that an unordered index doesn't * induce any ordering, so we return NIL.) * - * If 'scandir' is BackwardScanDirection, build pathkeys representing a - * backwards scan of the index. + * This always builds pathkeys for a forward scan of the index. For backward + * scans, the caller should use reverse_pathkeys() on the result. * * We iterate only key columns of covering indexes, since non-key columns * don't influence index ordering. The result is canonical, meaning that @@ -738,8 +786,7 @@ get_cheapest_parallel_safe_total_inner(List *paths) */ List * build_index_pathkeys(PlannerInfo *root, - IndexOptInfo *index, - ScanDirection scandir) + IndexOptInfo *index) { List *retval = NIL; ListCell *lc; @@ -767,16 +814,9 @@ build_index_pathkeys(PlannerInfo *root, /* We assume we don't need to make a copy of the tlist item */ indexkey = indextle->expr; - if (ScanDirectionIsBackward(scandir)) - { - reverse_sort = !index->reverse_sort[i]; - nulls_first = !index->nulls_first[i]; - } - else - { - reverse_sort = index->reverse_sort[i]; - nulls_first = index->nulls_first[i]; - } + /* Use the index's natural sort order (forward scan) */ + reverse_sort = index->reverse_sort[i]; + nulls_first = index->nulls_first[i]; /* * OK, try to make a canonical pathkey for this sort key. diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 051e38c1189..9c4569f2bc2 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -233,8 +233,9 @@ extern Path *get_cheapest_fractional_path_for_pathkeys(List *paths, Relids required_outer, double fraction); extern Path *get_cheapest_parallel_safe_total_inner(List *paths); -extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index, - ScanDirection scandir); +extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index); +extern PathKey *make_reversed_pathkey(PlannerInfo *root, PathKey *pathkey); +extern List *reverse_pathkeys(PlannerInfo *root, List *pathkeys); extern List *build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel, ScanDirection scandir, bool *partialkeys); extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr, -- 2.53.0