On 6/2/2024 13:51, Ashutosh Bapat wrote:
On Fri, Dec 15, 2023 at 5:22 AM Ashutosh Bapat
<ashutosh.bapat....@gmail.com> wrote:
First patch is no longer required. Here's rebased set
The patches are raw. make check has some crashes that I need to fix. I
am waiting to hear whether this is useful and whether the design is on
the right track.
I think this project is quite important to move forward and discover how
far we can go this way. In the attachment, the rebased patch set with
fixes allows it to pass the regression tests.
This idea of a refcounter resolves the problem with blind usage of
add_path. It is not only about extensions, which sometimes want to add
paths on different levels of the join tree and don't care about dangling
pointers. It is also about possible hidden bugs (a freed path staying in
the path list, just not employed) that aren't yet detected due to
costings and low test coverage.
--
regards,
Andrei Lepikhov
Postgres Professional
From 1b2a42da985fbd0287472ba3b348528d8931ba9d Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepi...@gmail.com>
Date: Thu, 26 Jun 2025 15:11:03 +0200
Subject: [PATCH 1/3] Basic infrastructure to link, unlink, and free pathnodes.
add_path and add_partial_path free path nodes that they deem suboptimal.
But they just free the uppermost pathnode in the path. The subpaths continue
to occupy memory even if they are not used anywhere. The subpath nodes are
not freed because other paths may reference them. This commit introduces
the infrastructure to track references to paths.
As the path nodes are referenced, they are "linked" using link_path() to
reference objects. When the path nodes are no longer helpful, they are
"unlinked" using unlink_path() from the referencing objects. The paths whose
references reach zero during unlinking are freed automatically using
the free_path() function. The function unlinks the sub-path nodes and can be
called when freeing any path node.
When the final path for the join tree is chosen, the paths linked to each
participating relation are unlinked, thus ultimately getting freed if not used.
TODO
The functions free_path(), unlink_path() and link_path() have ereports
to catch code paths that do not use these functions correctly. They call
errbacktrace(), which is not used anywhere else. These ereport calls will
need to be fixed for productization.
---
src/backend/optimizer/path/allpaths.c | 83 +++++++++++++++
src/backend/optimizer/util/pathnode.c | 142 ++++++++++++++++++++++++++
src/include/nodes/pathnodes.h | 1 +
src/include/optimizer/pathnode.h | 38 +++++++
4 files changed, 264 insertions(+)
diff --git a/src/backend/optimizer/path/allpaths.c
b/src/backend/optimizer/path/allpaths.c
index 6cc6966b060..a5d48397ac3 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -3418,6 +3418,87 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist)
}
}
+/*
+ * TODO: Need similar code to free paths in upper relations.
+ */
+static void
+free_unused_paths_from_rel(RelOptInfo *rel)
+{
+ ListCell *lc_path;
+ int cnt_part;
+
+ foreach(lc_path, rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc_path);
+
+ /* Free the path if none references it. */
+ if (path->ref_count == 1)
+ {
+ /* TODO: use routine to unlink path from list */
+ rel->pathlist = foreach_delete_current(rel->pathlist,
lc_path);
+ unlink_path(&path);
+ }
+ }
+
+ /* Do the same for partial pathlist. */
+ foreach(lc_path, rel->partial_pathlist)
+ {
+ Path *path = (Path *) lfirst(lc_path);
+
+ /* Free the path if none references it. */
+ if (path->ref_count == 1)
+ {
+ rel->partial_pathlist =
foreach_delete_current(rel->partial_pathlist, lc_path);
+ unlink_path(&path);
+ }
+ }
+
+ /*
+ * TODO: We can perform this in generate_partitionwise_paths as well
since
+ * by that time all the paths from partitions will be linked if used.
+ */
+
+ /* Free unused paths from the partition relations */
+ for (cnt_part = 0; cnt_part < rel->nparts; cnt_part++)
+ {
+ RelOptInfo *child_rel = rel->part_rels[cnt_part];
+
+ if (child_rel == NULL || IS_DUMMY_REL(child_rel))
+ /* Skip empty entries */
+ continue;
+
+ free_unused_paths_from_rel(child_rel);
+ }
+}
+
+/*
+ * Free unused paths from all the relations created while building the join
tree.
+ */
+static void
+free_unused_paths(PlannerInfo *root, int levels_needed)
+{
+ int cnt;
+
+ for (cnt = levels_needed - 1; cnt >= 1; cnt--)
+ {
+ ListCell *lc;
+
+ foreach(lc, root->join_rel_level[cnt])
+ {
+ free_unused_paths_from_rel(lfirst(lc));
+ }
+ }
+
+ for (cnt = 0; cnt < root->simple_rel_array_size; cnt++)
+ {
+ RelOptInfo *rel = root->simple_rel_array[cnt];
+
+ /* Skip empty slots */
+ if (rel != NULL)
+ free_unused_paths_from_rel(rel);
+ }
+}
+
/*
* standard_join_search
* Find possible joinpaths for a query by successively finding ways
@@ -3520,6 +3601,8 @@ standard_join_search(PlannerInfo *root, int
levels_needed, List *initial_rels)
}
}
+ free_unused_paths(root, levels_needed);
+
/*
* We should have a single rel at the final level.
*/
diff --git a/src/backend/optimizer/util/pathnode.c
b/src/backend/optimizer/util/pathnode.c
index e0192d4a491..fa13a8f8ff1 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -969,6 +969,148 @@ add_partial_path_precheck(RelOptInfo *parent_rel, int
disabled_nodes,
return true;
}
+void
+free_path(Path *path)
+{
+ /*
+ * If the path is still referenced, freeing it would create a dangling
+ * pointer.
+ */
+ /* TODO: it could just be an Assert? */
+ if (path->ref_count != 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errbacktrace(),
+ errmsg("path node %p of type %d has reference
count %d, can not be freed",
+ path, path->pathtype,
path->ref_count)));
+ return;
+ }
+
+ /*
+ * A path referenced in the parent relation's pathlist can't be freed.
+ * Ideally such a path should have ref_count >= 1. If this happens it
+ * indicates that the path was not linked somewhere, and yet unlinked
+ * (usually by free_path()).
+ */
+ if (list_member_ptr(path->parent->pathlist, path))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errbacktrace(),
+ errmsg("path node %p of type %d has reference
count %d but is linked in parent's pathlist, can not be freed",
+ path, path->pathtype,
path->ref_count)));
+ return;
+ }
+
+ /* Decrement the reference counts of paths referenced by this one. */
+ switch (path->pathtype)
+ {
+ case T_SeqScan:
+ case T_IndexScan:
+ case T_IndexOnlyScan:
+ /* Simple paths reference no other path. */
+ break;
+
+ case T_MergeJoin:
+ case T_HashJoin:
+ case T_NestLoop:
+ {
+ JoinPath *jpath = (JoinPath *) path;
+
+ unlink_path(&(jpath->outerjoinpath));
+ unlink_path(&(jpath->innerjoinpath));
+ }
+ break;
+
+ case T_Append:
+ case T_MergeAppend:
+ {
+ AppendPath *appath = (AppendPath *) path;
+ ListCell *lc;
+
+ foreach(lc, appath->subpaths)
+ {
+ Path *subpath = lfirst(lc);
+
+ /* TODO use a routine to unlink path
from list. */
+ appath->subpaths =
foreach_delete_current(appath->subpaths, lc);
+ unlink_path(&subpath);
+ }
+ }
+ break;
+
+ case T_Gather:
+ {
+ GatherPath *gpath = (GatherPath *) path;
+
+ unlink_path(&(gpath->subpath));
+ }
+ break;
+
+ case T_GatherMerge:
+ {
+ GatherMergePath *gmpath = (GatherMergePath *)
path;
+
+ unlink_path(&gmpath->subpath);
+ }
+ break;
+
+ case T_BitmapHeapScan:
+ {
+ BitmapHeapPath *bhpath = (BitmapHeapPath *)
path;
+
+ unlink_path(&(bhpath->bitmapqual));
+ }
+ break;
+
+ case T_Material:
+ {
+ MaterialPath *mpath = (MaterialPath *) path;
+
+ unlink_path(&(mpath->subpath));
+ }
+ break;
+
+ case T_Memoize:
+ {
+ MemoizePath *mpath = (MemoizePath *) path;
+
+ unlink_path(&mpath->subpath);
+ }
+ break;
+
+ case T_Result:
+ {
+ /*
+ * All Result paths except ProjectionPath are
simple paths
+ * without any subpath.
+ */
+ if (IsA(path, ProjectionPath))
+ {
+ ProjectionPath *ppath = (ProjectionPath
*) path;
+
+ unlink_path(&ppath->subpath);
+ }
+ }
+ break;
+
+ default:
+ ereport(WARNING,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errbacktrace(),
+ errmsg("unrecognized path type %d",
path->pathtype)));
+ break;
+ }
+
+ /*
+ * TODO: add_path does not free IndexPaths, but we do that here. Is
there
+ * a hazard?
+ */
+
+ /* Now reclaim the memory. */
+ pfree(path);
+}
/*****************************************************************************
* PATH NODE CREATION ROUTINES
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6567759595d..c88323a915b 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1797,6 +1797,7 @@ typedef struct Path
/* sort ordering of path's output; a List of PathKey nodes; see above */
List *pathkeys;
+ int ref_count;
} Path;
/* Macro for extracting a path's parameterization relids; beware double eval */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 60dcdb77e41..22ee1c2261b 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -16,6 +16,7 @@
#include "nodes/bitmapset.h"
#include "nodes/pathnodes.h"
+#include "limits.h"
/*
@@ -306,6 +307,43 @@ extern Path *reparameterize_path_by_child(PlannerInfo
*root, Path *path,
RelOptInfo *child_rel);
extern bool path_is_reparameterizable_by_child(Path *path,
RelOptInfo *child_rel);
+extern void free_path(Path *path);
+
+static inline void
+link_path(Path **path_link, Path *path)
+{
+ if (path->ref_count < 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errbacktrace(),
+ errmsg("path node %p of type %d has negative
reference count %d",
+ path, path->pathtype,
path->ref_count)));
+
+ path->ref_count++;
+ *path_link = path;
+ Assert(path->ref_count > 0 && path->ref_count <= INT_MAX);
+}
+
+static inline void
+unlink_path(Path **path_link)
+{
+ Path *path = *path_link;
+
+ /* A path to be unlinked should have been linked before. */
+ if (path->ref_count < 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errbacktrace(),
+ errmsg("path node %p of type %d had negative
reference count %d",
+ path, path->pathtype,
path->ref_count)));
+
+ path->ref_count--;
+ *path_link = NULL;
+
+ /* Free path if none is referencing it. */
+ if (path->ref_count == 0)
+ free_path(path);
+}
/*
* prototypes for relnode.c
--
2.50.0
From a376145f3770b0fc2ea9b828e074dc522c1abaf7 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepi...@gmail.com>
Date: Fri, 27 Jun 2025 11:42:06 +0200
Subject: [PATCH 2/3] Actual code to use pathnode referencing infrastructure
The commit uses the infrastructure built by the previous commit to references,
unreference and free paths.
The code is not completely mature. There are TODOs.
---
src/backend/optimizer/path/allpaths.c | 8 +-
src/backend/optimizer/util/pathnode.c | 212 +++++++++++++++++---------
src/include/optimizer/pathnode.h | 8 +-
3 files changed, 147 insertions(+), 81 deletions(-)
diff --git a/src/backend/optimizer/path/allpaths.c
b/src/backend/optimizer/path/allpaths.c
index a5d48397ac3..33b12ed50f9 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -3432,11 +3432,11 @@ free_unused_paths_from_rel(RelOptInfo *rel)
Path *path = (Path *) lfirst(lc_path);
/* Free the path if none references it. */
- if (path->ref_count == 1)
+ if (path->ref_count == 0)
{
/* TODO: use routine to unlink path from list */
rel->pathlist = foreach_delete_current(rel->pathlist,
lc_path);
- unlink_path(&path);
+ unlink_path(&path, 0, true, true);
}
}
@@ -3446,10 +3446,10 @@ free_unused_paths_from_rel(RelOptInfo *rel)
Path *path = (Path *) lfirst(lc_path);
/* Free the path if none references it. */
- if (path->ref_count == 1)
+ if (path->ref_count == 0)
{
rel->partial_pathlist =
foreach_delete_current(rel->partial_pathlist, lc_path);
- unlink_path(&path);
+ unlink_path(&path, 0, true, true);
}
}
diff --git a/src/backend/optimizer/util/pathnode.c
b/src/backend/optimizer/util/pathnode.c
index fa13a8f8ff1..0d820143ba3 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -627,10 +627,10 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
p1);
/*
- * Delete the data pointed-to by the deleted cell, if
possible
+ * TODO: write a routine to unlink a path from the list
node and
+ * delete the list node
*/
- if (!IsA(old_path, IndexPath))
- pfree(old_path);
+ unlink_path(&old_path, 0, false, false);
}
else
{
@@ -658,12 +658,14 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
/* Accept the new path: insert it at proper place in pathlist */
parent_rel->pathlist =
list_insert_nth(parent_rel->pathlist, insert_at,
new_path);
+ new_path->ref_count++;
+ /* TODO: write a function to link_path in a List */
}
else
{
/* Reject and recycle the new path */
if (!IsA(new_path, IndexPath))
- pfree(new_path);
+ free_path(new_path, 0, false);
}
}
@@ -876,7 +878,8 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path)
{
parent_rel->partial_pathlist =
foreach_delete_current(parent_rel->partial_pathlist, p1);
- pfree(old_path);
+ /* TODO use routine to unlink a path from the linked
list */
+ unlink_path(&old_path, 0, false, false);
}
else
{
@@ -899,11 +902,13 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path)
/* Accept the new path: insert it at proper place */
parent_rel->partial_pathlist =
list_insert_nth(parent_rel->partial_pathlist,
insert_at, new_path);
+ new_path->ref_count++;
+ /* TODO create a routine to link path in a list at a given
place */
}
else
{
/* Reject and recycle the new path */
- pfree(new_path);
+ free_path(new_path, 0, false);
}
}
@@ -970,8 +975,13 @@ add_partial_path_precheck(RelOptInfo *parent_rel, int
disabled_nodes,
}
void
-free_path(Path *path)
+free_path(Path *path, int level, bool recurse)
{
+ if (!recurse && level > 0)
+ return;
+
+ level++;
+
/*
* If the path is still referenced, freeing it would create a dangling
* pointer.
@@ -979,11 +989,6 @@ free_path(Path *path)
/* TODO: it could just be an Assert? */
if (path->ref_count != 0)
{
- ereport(WARNING,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errbacktrace(),
- errmsg("path node %p of type %d has reference
count %d, can not be freed",
- path, path->pathtype,
path->ref_count)));
return;
}
@@ -995,11 +1000,6 @@ free_path(Path *path)
*/
if (list_member_ptr(path->parent->pathlist, path))
{
- ereport(WARNING,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errbacktrace(),
- errmsg("path node %p of type %d has reference
count %d but is linked in parent's pathlist, can not be freed",
- path, path->pathtype,
path->ref_count)));
return;
}
@@ -1018,8 +1018,8 @@ free_path(Path *path)
{
JoinPath *jpath = (JoinPath *) path;
- unlink_path(&(jpath->outerjoinpath));
- unlink_path(&(jpath->innerjoinpath));
+ unlink_path(&(jpath->outerjoinpath), level,
recurse, true);
+ unlink_path(&(jpath->innerjoinpath), level,
recurse, true);
}
break;
@@ -1035,7 +1035,7 @@ free_path(Path *path)
/* TODO use a routine to unlink path
from list. */
appath->subpaths =
foreach_delete_current(appath->subpaths, lc);
- unlink_path(&subpath);
+ unlink_path(&subpath, level, recurse,
true);
}
}
break;
@@ -1044,7 +1044,7 @@ free_path(Path *path)
{
GatherPath *gpath = (GatherPath *) path;
- unlink_path(&(gpath->subpath));
+ unlink_path(&(gpath->subpath), level, recurse,
true);
}
break;
@@ -1052,7 +1052,7 @@ free_path(Path *path)
{
GatherMergePath *gmpath = (GatherMergePath *)
path;
- unlink_path(&gmpath->subpath);
+ unlink_path(&gmpath->subpath, level, recurse,
true);
}
break;
@@ -1060,7 +1060,7 @@ free_path(Path *path)
{
BitmapHeapPath *bhpath = (BitmapHeapPath *)
path;
- unlink_path(&(bhpath->bitmapqual));
+ unlink_path(&(bhpath->bitmapqual), level,
recurse, true);
}
break;
@@ -1068,7 +1068,7 @@ free_path(Path *path)
{
MaterialPath *mpath = (MaterialPath *) path;
- unlink_path(&(mpath->subpath));
+ unlink_path(&(mpath->subpath), level, recurse,
true);
}
break;
@@ -1076,7 +1076,7 @@ free_path(Path *path)
{
MemoizePath *mpath = (MemoizePath *) path;
- unlink_path(&mpath->subpath);
+ unlink_path(&mpath->subpath, level, recurse,
true);
}
break;
@@ -1090,16 +1090,64 @@ free_path(Path *path)
{
ProjectionPath *ppath = (ProjectionPath
*) path;
- unlink_path(&ppath->subpath);
+ unlink_path(&ppath->subpath, level,
recurse, true);
}
}
break;
+ case T_Agg:
+ {
+ AggPath *apath = (AggPath *) path;
+
+ unlink_path(&apath->subpath, level, recurse, true);
+ }
+ break;
+ case T_IncrementalSort:
+ {
+ IncrementalSortPath *ipath = (IncrementalSortPath *)
path;
+
+ unlink_path(&ipath->spath.subpath, level, recurse,
true);
+ }
+ break;
+ case T_WindowAgg:
+ {
+ WindowAggPath *wpath = (WindowAggPath *) path;
+
+ unlink_path(&wpath->subpath, level, recurse, true);
+ }
+ break;
+ case T_SubqueryScan:
+ {
+ SubqueryScanPath *spath = (SubqueryScanPath *) path;
+
+ unlink_path(&spath->subpath, level, recurse, true);
+ }
+ break;
+ case T_Unique:
+ {
+ UniquePath *upath = (UniquePath *) path;
+ unlink_path(&upath->subpath, level, recurse, true);
+ }
+ break;
+ case T_Limit:
+ {
+ LimitPath *lpath = (LimitPath *) path;
+
+ unlink_path(&lpath->subpath, level, recurse, true);
+ }
+ break;
+ case T_Group:
+ {
+ GroupPath *gpath = (GroupPath *) path;
+
+ unlink_path(&gpath->subpath, level, recurse, true);
+ }
+ break;
default:
- ereport(WARNING,
+ ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errbacktrace(),
- errmsg("unrecognized path type %d",
path->pathtype)));
+ errmsg("unrecognized path type1 %d",
path->pathtype)));
break;
}
@@ -1256,7 +1304,7 @@ create_bitmap_heap_path(PlannerInfo *root,
pathnode->path.parallel_workers = parallel_degree;
pathnode->path.pathkeys = NIL; /* always unordered */
- pathnode->bitmapqual = bitmapqual;
+ link_path(&(pathnode->bitmapqual), bitmapqual);
cost_bitmap_heap_scan(&pathnode->path, root, rel,
pathnode->path.param_info,
@@ -1291,6 +1339,8 @@ create_bitmap_and_path(PlannerInfo *root,
{
Path *bitmapqual = (Path *) lfirst(lc);
+ /* TODO: find a better way to link a path in a linked list */
+ bitmapqual->ref_count++;
required_outer = bms_add_members(required_outer,
PATH_REQ_OUTER(bitmapqual));
}
@@ -1343,6 +1393,8 @@ create_bitmap_or_path(PlannerInfo *root,
{
Path *bitmapqual = (Path *) lfirst(lc);
+ /* TODO: find a better way to link a path in a linked list */
+ bitmapqual->ref_count++;
required_outer = bms_add_members(required_outer,
PATH_REQ_OUTER(bitmapqual));
}
@@ -1516,6 +1568,8 @@ create_append_path(PlannerInfo *root,
{
Path *subpath = (Path *) lfirst(l);
+ /* TODO: we should use the routine to link path to list */
+ subpath->ref_count++;
pathnode->path.parallel_safe = pathnode->path.parallel_safe &&
subpath->parallel_safe;
@@ -1661,6 +1715,9 @@ create_merge_append_path(PlannerInfo *root,
/* All child paths should be unparameterized */
Assert(bms_is_empty(PATH_REQ_OUTER(subpath)));
+ /* TODO: use routine which links a path into a list */
+ subpath->ref_count++;
+
pathnode->path.rows += subpath->rows;
pathnode->path.parallel_safe = pathnode->path.parallel_safe &&
subpath->parallel_safe;
@@ -1789,7 +1846,7 @@ create_material_path(RelOptInfo *rel, Path *subpath)
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
cost_material(&pathnode->path,
subpath->disabled_nodes,
@@ -1824,7 +1881,7 @@ create_memoize_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->hash_operators = hash_operators;
pathnode->param_exprs = param_exprs;
pathnode->singlerow = singlerow;
@@ -1919,7 +1976,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
*/
pathnode->path.pathkeys = NIL;
- pathnode->subpath = subpath;
+ link_path(&(pathnode->subpath), subpath);
/*
* Under GEQO and when planning child joins, the sjinfo might be
@@ -1947,7 +2004,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
- rel->cheapest_unique_path = (Path *) pathnode;
+ /* TODO: Do we need to make sure that cheapest_unique_path is
NULL. */
+ link_path(&(rel->cheapest_unique_path), (Path *) pathnode);
MemoryContextSwitchTo(oldcontext);
@@ -1986,7 +2044,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
- rel->cheapest_unique_path = (Path *) pathnode;
+ link_path(&(rel->cheapest_unique_path), (Path
*) pathnode);
MemoryContextSwitchTo(oldcontext);
@@ -2087,7 +2145,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
pathnode->path.total_cost = sort_path.total_cost;
}
- rel->cheapest_unique_path = (Path *) pathnode;
+ link_path(&(rel->cheapest_unique_path), (Path *) pathnode);
MemoryContextSwitchTo(oldcontext);
@@ -2129,7 +2187,7 @@ create_gather_merge_path(PlannerInfo *root, RelOptInfo
*rel, Path *subpath,
required_outer);
pathnode->path.parallel_aware = false;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->num_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
pathnode->path.pathtarget = target ? target : rel->reltarget;
@@ -2200,7 +2258,7 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL; /* Gather has unordered result */
- pathnode->subpath = subpath;
+ link_path(&(pathnode->subpath), subpath);
pathnode->num_workers = subpath->parallel_workers;
pathnode->single_copy = false;
@@ -2243,7 +2301,7 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo
*rel, Path *subpath,
subpath->parallel_safe;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
cost_subqueryscan(pathnode, root, rel, pathnode->path.param_info,
trivial_pathtarget);
@@ -2475,7 +2533,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo
*rel,
pathnode->path.total_cost = total_cost;
pathnode->path.pathkeys = pathkeys;
- pathnode->fdw_outerpath = fdw_outerpath;
+ link_path(&pathnode->fdw_outerpath, fdw_outerpath);
pathnode->fdw_restrictinfo = fdw_restrictinfo;
pathnode->fdw_private = fdw_private;
@@ -2529,7 +2587,7 @@ create_foreign_join_path(PlannerInfo *root, RelOptInfo
*rel,
pathnode->path.total_cost = total_cost;
pathnode->path.pathkeys = pathkeys;
- pathnode->fdw_outerpath = fdw_outerpath;
+ link_path(&pathnode->fdw_outerpath, fdw_outerpath);
pathnode->fdw_restrictinfo = fdw_restrictinfo;
pathnode->fdw_private = fdw_private;
@@ -2578,7 +2636,7 @@ create_foreign_upper_path(PlannerInfo *root, RelOptInfo
*rel,
pathnode->path.total_cost = total_cost;
pathnode->path.pathkeys = pathkeys;
- pathnode->fdw_outerpath = fdw_outerpath;
+ link_path(&pathnode->fdw_outerpath, fdw_outerpath);
pathnode->fdw_restrictinfo = fdw_restrictinfo;
pathnode->fdw_private = fdw_private;
@@ -2741,8 +2799,8 @@ create_nestloop_path(PlannerInfo *root,
pathnode->jpath.path.pathkeys = pathkeys;
pathnode->jpath.jointype = jointype;
pathnode->jpath.inner_unique = extra->inner_unique;
- pathnode->jpath.outerjoinpath = outer_path;
- pathnode->jpath.innerjoinpath = inner_path;
+ link_path(&(pathnode->jpath.outerjoinpath), outer_path);
+ link_path(&(pathnode->jpath.innerjoinpath), inner_path);
pathnode->jpath.joinrestrictinfo = restrict_clauses;
final_cost_nestloop(root, pathnode, workspace, extra);
@@ -2807,8 +2865,8 @@ create_mergejoin_path(PlannerInfo *root,
pathnode->jpath.path.pathkeys = pathkeys;
pathnode->jpath.jointype = jointype;
pathnode->jpath.inner_unique = extra->inner_unique;
- pathnode->jpath.outerjoinpath = outer_path;
- pathnode->jpath.innerjoinpath = inner_path;
+ link_path(&(pathnode->jpath.outerjoinpath), outer_path);
+ link_path(&(pathnode->jpath.innerjoinpath), inner_path);
pathnode->jpath.joinrestrictinfo = restrict_clauses;
pathnode->path_mergeclauses = mergeclauses;
pathnode->outersortkeys = outersortkeys;
@@ -2885,8 +2943,8 @@ create_hashjoin_path(PlannerInfo *root,
pathnode->jpath.path.pathkeys = NIL;
pathnode->jpath.jointype = jointype;
pathnode->jpath.inner_unique = extra->inner_unique;
- pathnode->jpath.outerjoinpath = outer_path;
- pathnode->jpath.innerjoinpath = inner_path;
+ link_path(&(pathnode->jpath.outerjoinpath), outer_path);
+ link_path(&(pathnode->jpath.innerjoinpath), inner_path);
pathnode->jpath.joinrestrictinfo = restrict_clauses;
pathnode->path_hashclauses = hashclauses;
/* final_cost_hashjoin will fill in pathnode->num_batches */
@@ -2927,6 +2985,9 @@ create_projection_path(PlannerInfo *root,
Assert(subpp->path.parent == rel);
subpath = subpp->subpath;
Assert(!IsA(subpath, ProjectionPath));
+
+ /* Free it if not used anywhere else. */
+ unlink_path((Path **) &subpp, 0, false, false);
}
pathnode->path.pathtype = T_Result;
@@ -2942,7 +3003,7 @@ create_projection_path(PlannerInfo *root,
/* Projection does not change the sort order */
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
/*
* We might not need a separate Result node. If the input plan node
type
@@ -3063,21 +3124,21 @@ apply_projection_to_path(PlannerInfo *root,
{
GatherPath *gpath = (GatherPath *) path;
- gpath->subpath = (Path *)
- create_projection_path(root,
-
gpath->subpath->parent,
-
gpath->subpath,
-
target);
+ link_path(&gpath->subpath,
+ (Path *) create_projection_path(root,
+
gpath->subpath->parent,
+
gpath->subpath,
+
target));
}
else
{
GatherMergePath *gmpath = (GatherMergePath *) path;
- gmpath->subpath = (Path *)
- create_projection_path(root,
-
gmpath->subpath->parent,
-
gmpath->subpath,
-
target);
+ link_path(&gmpath->subpath,
+ (Path *) create_projection_path(root,
+
gmpath->subpath->parent,
+
gmpath->subpath,
+
target));
}
}
else if (path->parallel_safe &&
@@ -3126,7 +3187,7 @@ create_set_projection_path(PlannerInfo *root,
/* Projection does not change the sort order XXX? */
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
/*
* Estimate number of rows produced by SRFs for each row of input; if
@@ -3196,7 +3257,7 @@ create_incremental_sort_path(PlannerInfo *root,
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
cost_incremental_sort(&pathnode->path,
root, pathkeys,
presorted_keys,
@@ -3244,7 +3305,7 @@ create_sort_path(PlannerInfo *root,
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
cost_sort(&pathnode->path, root, pathkeys,
subpath->disabled_nodes,
@@ -3291,7 +3352,7 @@ create_group_path(PlannerInfo *root,
/* Group doesn't change sort ordering */
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->groupClause = groupClause;
pathnode->qual = qual;
@@ -3350,7 +3411,7 @@ create_upper_unique_path(PlannerInfo *root,
/* Unique doesn't change the input ordering */
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->numkeys = numCols;
/*
@@ -3424,7 +3485,7 @@ create_agg_path(PlannerInfo *root,
else
pathnode->path.pathkeys = NIL; /* output is unordered */
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->aggstrategy = aggstrategy;
pathnode->aggsplit = aggsplit;
@@ -3488,7 +3549,7 @@ create_groupingsets_path(PlannerInfo *root,
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_workers = subpath->parallel_workers;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
/*
* Simplify callers by downgrading AGG_SORTED to AGG_PLAIN, and
AGG_MIXED
@@ -3746,7 +3807,7 @@ create_windowagg_path(PlannerInfo *root,
/* WindowAgg preserves the input sort order */
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->winclause = winclause;
pathnode->qual = qual;
pathnode->runCondition = runCondition;
@@ -3818,8 +3879,9 @@ create_setop_path(PlannerInfo *root,
pathnode->path.pathkeys =
(strategy == SETOP_SORTED) ? leftpath->pathkeys : NIL;
- pathnode->leftpath = leftpath;
- pathnode->rightpath = rightpath;
+ link_path(&pathnode->leftpath, leftpath);
+ link_path(&pathnode->rightpath, rightpath);
+
pathnode->cmd = cmd;
pathnode->strategy = strategy;
pathnode->groupList = groupList;
@@ -3934,8 +3996,8 @@ create_recursiveunion_path(PlannerInfo *root,
/* RecursiveUnion result is always unsorted */
pathnode->path.pathkeys = NIL;
- pathnode->leftpath = leftpath;
- pathnode->rightpath = rightpath;
+ link_path(&pathnode->leftpath, leftpath);
+ link_path(&pathnode->rightpath, rightpath);
pathnode->distinctList = distinctList;
pathnode->wtParam = wtParam;
pathnode->numGroups = numGroups;
@@ -3977,7 +4039,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
*/
pathnode->path.pathkeys = NIL;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->rowMarks = rowMarks;
pathnode->epqParam = epqParam;
@@ -4084,7 +4146,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo
*rel,
pathnode->path.pathtarget->width = 0;
}
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->operation = operation;
pathnode->canSetTag = canSetTag;
pathnode->nominalRelation = nominalRelation;
@@ -4144,7 +4206,7 @@ create_limit_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
- pathnode->subpath = subpath;
+ link_path(&pathnode->subpath, subpath);
pathnode->limitOffset = limitOffset;
pathnode->limitCount = limitCount;
pathnode->limitOption = limitOption;
@@ -4430,6 +4492,7 @@ do { \
(path) = reparameterize_path_by_child(root, (path), child_rel); \
if ((path) == NULL) \
return NULL; \
+ link_path(&(path), (path)); \
} while(0)
#define REPARAMETERIZE_CHILD_PATH_LIST(pathlist) \
@@ -4852,11 +4915,14 @@ reparameterize_pathlist_by_child(PlannerInfo *root,
if (path == NULL)
{
+ /* TODO: unlink paths in the list */
list_free(result);
return NIL;
}
+ /* TODO: need a routine to link a path into a linked list */
result = lappend(result, path);
+ path->ref_count++;
}
return result;
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 22ee1c2261b..da37771bf86 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -307,7 +307,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo
*root, Path *path,
RelOptInfo *child_rel);
extern bool path_is_reparameterizable_by_child(Path *path,
RelOptInfo *child_rel);
-extern void free_path(Path *path);
+extern void free_path(Path *path, int level, bool recurse);
static inline void
link_path(Path **path_link, Path *path)
@@ -325,7 +325,7 @@ link_path(Path **path_link, Path *path)
}
static inline void
-unlink_path(Path **path_link)
+unlink_path(Path **path_link, int level, bool recurse, bool need_free)
{
Path *path = *path_link;
@@ -341,8 +341,8 @@ unlink_path(Path **path_link)
*path_link = NULL;
/* Free path if none is referencing it. */
- if (path->ref_count == 0)
- free_path(path);
+ if (path->ref_count == 0 && need_free)
+ free_path(path, level, recurse);
}
/*
--
2.50.0
From 0f5a5bd14ce2791f24f855f8176b470a0ba8884d Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepi...@gmail.com>
Date: Fri, 27 Jun 2025 11:46:16 +0200
Subject: [PATCH 3/3] Local variables pointing to path node used repeatedly
In match_unsorted_outer() we create a materialize path for inner
relation and pass it to try_nestloop_path repeatedly. Link and unlink
the path to and from the local variable to keep track of it.
---
src/backend/optimizer/path/joinpath.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/backend/optimizer/path/joinpath.c
b/src/backend/optimizer/path/joinpath.c
index 7aa8f5d799c..275fe9cfb58 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -1892,12 +1892,14 @@ match_unsorted_outer(PlannerInfo *root,
/*
* Consider materializing the cheapest inner path, unless
* enable_material is off or the path in question materializes
its
- * output anyway.
+ * output anyway. Link the path to a local reference so that it
won't
+ * be freed by add_path if the surrounding nest path is freed by
+ * add_path. It will get freed if not used later.
*/
if (enable_material && inner_cheapest_total != NULL &&
!ExecMaterializesOutput(inner_cheapest_total->pathtype))
- matpath = (Path *)
- create_material_path(innerrel,
inner_cheapest_total);
+ link_path(&matpath,
+ (Path *)
create_material_path(innerrel, inner_cheapest_total));
}
foreach(lc1, outerrel->pathlist)
@@ -2013,6 +2015,10 @@ match_unsorted_outer(PlannerInfo *root,
false);
}
+ /* Materialized inner path won't be used anymore. Unlink it */
+ if (matpath)
+ unlink_path(&matpath, 0, false, true);
+
/*
* Consider partial nestloop and mergejoin plan if outerrel has any
* partial path and the joinrel is parallel-safe. However, we can't
--
2.50.0