Sorry for the delay in my response.

On Wed, May 29, 2024 at 9:34 AM Ashutosh Bapat
<ashutosh.bapat....@gmail.com> wrote:
>
> Documenting some comments from todays' patch review session

I forgot to mention back then that both of the suggestions below came
from Tom Lane.

> 1. Instead of a nested hash table, it might be better to use a flat hash 
> table to save more memory.

Done. It indeed saves memory without impacting planning time.

> 2. new comm_rinfo member in RestrictInfo may have problems when copying 
> RestrictInfo or translating it. Instead commuted versions may be tracked 
> outside RestrictInfo

After commit 767c598954bbf72e0535f667e2e0667765604b2a,
repameterization of parent paths for child relation happen at the time
of creating the plan. This reduces the number of child paths produced
by reparameterization and also reduces the number of RestrictInfos
that get translated during reparameterization. During
reparameterization commuted parent RestrictInfos are required to be
translated to child RestrictInfos. Before the commit, this led to
translating the same commuted parent RestrictInfo multiple times.
After the commit, only one path gets reparameterized for a given
parent and child pair. Hence we do not produce multiple copies of the
same commuted child RestrictInfo. Hence we don't need to keep track of
commuted child RestrictInfos anymore. Removed that portion of code
from the patches.

I made detailed memory consumption measurements with this patch for
number of partitions changed from 0 (unpartitioned) to 1000 and for 2
to 5-way joins. They are available in the spreadsheet at [1]. The raw
measurement data is in the first sheet named "pwj_mem_measurements raw
numbers". The averages over multiple runs are in second sheet named
"avg_numbers". Rest of the sheet represent the averages in more
consumable manner. Please note that the averages make sense only for
planning time since the memory consumption remains same with every
run. Also note that EXPLAIN now reports planning memory consumption in
kB. Any changes to memory consumption below 1kB are not reported and
hence not noticed. Here are some observations.

1. When partitionwise join is not enabled, no changes to planner's
memory consumption are observed. See sheet named "pwj disabled,
planning memory".

2. When partitionwise join is enabled, upto 17% (206MB) memory is
saved by the patch in case of 5-way self-join with 1000 partitions.
This is the maximum memory saving observed. The amount of memory saved
increases with the number of joins and number of partitions. See sheet
with name "pwj enabled, planning memory"

3. After commit 767c598954bbf72e0535f667e2e0667765604b2a, we do not
translate a parent RestrictInfo multiple times for the same
parent-child pair in case of  a *2-way partitionwise join*. But we
still consume memory in saving the child RestrictInfo in the hash
table. Hence in case of 2-way join we see increased memory consumption
with the patch compared to master. The memory consumption increases by
13kb, 23kB, 76kB and 146kB for 10, 100, 500, 1000 partitions
respectively. This increase is smaller compared to the overall memory
saving. In order to avoid this memory increase, we will need to avoid
using hash table for 2-way join. We will need to know whether there
will be more than one partitionwise join before translating the
RestrictInfos for the first partitionwise join. This is hard to
achieve in all the cases since the decision to use partitionwise join
happens at the time of creating paths for a given join relation, which
itself is computed on the fly. We may choose some heuristics which
take into account the number of partitioned tables in the query, their
partition schemes, and the quals in the query to decide whether or not
to track the translated child RestrictInfos. But that will make the
code more complex, but more importantly the heuristics may not be able
to keep up if we start using partitionwise join as an optimization
strategy for more cases (e.g. asymmetric partitionwise join [2]). The
attached patch looks like a good tradeoff to me. But other opinions
might vary. Suggestions are welcome.

4. There is no noticeable change in the planning time. I ran the same
experiment multiple times. The planning time variations from each
experiment do not show any noticeable pattern suggesting increase or
decrease in the planning time with the patch.

A note about the code: I have added all the structures and functions
dealing with the RestrictInfo hash table at the end of restrictinfo.c.
I have not come across a C file in PostgreSQL code base where private
structures are defined in the middle the file; usually they are
defined at the beginning of the file. But I have chosen it that way
here since it makes it easy to document the hash table and the
functions at one place at the beginning of this code section. I am
open to suggestions which make the documentation easy while placing
the structures at the beginning of the file.

[1] 
https://docs.google.com/spreadsheets/d/1CEjRWZ02vuR8fSwhYaNugewtX8f0kIm5pLoRA95f3s8/edit?usp=sharing
[2] 
https://www.postgresql.org/message-id/flat/CAOP8fzaVL_2SCJayLL9kj5pCA46PJOXXjuei6-3aFUV45j4LJQ%40mail.gmail.com


--
Best Wishes,
Ashutosh Bapat
From c98b9efdca5e9201df3aa233c3e49f8e344b22b4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Mon, 4 Sep 2023 14:56:21 +0530
Subject: [PATCH 1/2] Avoid translating RestrictInfo repeatedly

RestrictInfo for the child relations (including the child join
relations) are obtained by translating RestrictInfo applicable to the
parent rel. Since these translations are not tracked, the same
RestrictInfo may get translated multiple times for the same parent and
child pair. When using partitionwise join this can happen as many times
as the number of possible join orders between the partitioned tables and
as many times a parent path is reparameterized for a child if a clause
is used in such a path.

Repeated translations are avoided by saving them in a hash table hanging
off of PlannerInfo.  RestrictInfo::rinfo_serial and
RestrictInfo::required_relids are used to compute hash key since they
together identify a RestrictInfo uniquely.  Two functions
find_child_rinfo() and add_child_rinfo() are used to search a
RestrictInfo for a given tanslation and add a child RestrictInfo
respectively.

Ashutosh Bapat
---
 src/backend/optimizer/path/equivclass.c   |  18 +-
 src/backend/optimizer/path/joinrels.c     |   7 +-
 src/backend/optimizer/util/pathnode.c     | 113 +++++++++-
 src/backend/optimizer/util/relnode.c      |   7 +-
 src/backend/optimizer/util/restrictinfo.c | 256 +++++++++++++++++++++-
 src/include/nodes/pathnodes.h             |   6 +
 src/include/optimizer/restrictinfo.h      |  10 +
 src/tools/pgindent/typedefs.list          |   3 +
 8 files changed, 400 insertions(+), 20 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index d871396e20..bb1f6c13c1 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -27,6 +27,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/cost.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
 #include "rewrite/rewriteManip.h"
@@ -1900,10 +1901,6 @@ create_join_clause(PlannerInfo *root,
 		rinfo->clause_relids = bms_add_members(rinfo->clause_relids,
 											   rightem->em_relids);
 
-	/* If it's a child clause, copy the parent's rinfo_serial */
-	if (parent_rinfo)
-		rinfo->rinfo_serial = parent_rinfo->rinfo_serial;
-
 	/* Mark the clause as redundant, or not */
 	rinfo->parent_ec = parent_ec;
 
@@ -1922,6 +1919,19 @@ create_join_clause(PlannerInfo *root,
 
 	MemoryContextSwitchTo(oldcontext);
 
+	if (parent_rinfo)
+	{
+		/* Copy parent's rinfo_serial to child RestrictInfo. */
+		rinfo->rinfo_serial = parent_rinfo->rinfo_serial;
+
+		/*
+		 * We have obtained a translation through EC machinery. Paritionwise
+		 * join will require the same translation.
+		 */
+		if (enable_partitionwise_join)
+			add_restrictinfo(root, rinfo);
+	}
+
 	return rinfo;
 }
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 7db5e30eef..2aebdf0078 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -19,6 +19,7 @@
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/restrictinfo.h"
 #include "partitioning/partbounds.h"
 #include "utils/memutils.h"
 
@@ -1651,10 +1652,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 		 * Construct restrictions applicable to the child join from those
 		 * applicable to the parent join.
 		 */
-		child_restrictlist =
-			(List *) adjust_appendrel_attrs(root,
-											(Node *) parent_restrictlist,
-											nappinfos, appinfos);
+		child_restrictlist = get_child_restrictinfos(root, parent_restrictlist,
+													 nappinfos, appinfos);
 
 		/* Find or construct the child join's RelOptInfo */
 		child_joinrel = joinrel->part_rels[cnt_parts];
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 54e042a8a5..a01ef273a5 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -26,6 +26,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "utils/memutils.h"
@@ -4080,6 +4081,78 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	return NULL;
 }
 
+/*
+ * build_child_iclauses_multilevel
+ *		Translate IndexClause list applicable to the given top parent relation to
+ *   that applicable to the given child relation where the child relation may
+ *   be several levels below the top parent in the partition hierarchy.
+ *
+ * This is similar to adjust_appendrel_attrs_multilevel() for IndexClause
+ * except that it uses translated RestrictInfos if already available.
+ */
+static List *
+build_child_iclauses_multilevel(PlannerInfo *root,
+								List *parent_iclauselist,
+								RelOptInfo *childrel,
+								RelOptInfo *top_parent)
+{
+	List	   *child_iclauses = NIL;
+
+	foreach_node(IndexClause, iclause, parent_iclauselist)
+	{
+		List	   *rinfo_list = NIL;
+		List	   *child_rinfo_list;
+		IndexClause *child_iclause = makeNode(IndexClause);
+		ListCell   *lcp;
+		ListCell   *lcc;
+		List	   *indexquals;
+
+		memcpy(child_iclause, iclause, sizeof(IndexClause));
+
+		/*
+		 * Collect RestrictInfos to be translated. That's all there's to
+		 * translate in an IndexClause.
+		 */
+		rinfo_list = lappend(rinfo_list, iclause->rinfo);
+		rinfo_list = list_concat(rinfo_list, iclause->indexquals);
+
+		child_rinfo_list = get_child_restrictinfos_multilevel(root, rinfo_list,
+															  childrel, top_parent);
+		child_iclause->rinfo = linitial(child_rinfo_list);
+		child_iclause->indexquals = NIL;
+		indexquals = list_delete_first(child_rinfo_list);
+
+		/*
+		 * indexquals of parent indexclause may have commuted RestrictInfos.
+		 * Commute the child indexquals accordingly.
+		 */
+		forboth(lcc, indexquals, lcp, iclause->indexquals)
+		{
+			RestrictInfo *child_rinfo = lfirst_node(RestrictInfo, lcc);
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lcp);
+			Relids		child_left_relids;
+
+			child_left_relids = adjust_child_relids_multilevel(root, rinfo->left_relids,
+															   childrel, top_parent);
+			if (!bms_equal(child_left_relids, child_rinfo->left_relids))
+			{
+				OpExpr	   *clause = castNode(OpExpr, rinfo->clause);
+
+				Assert(bms_equal(child_left_relids, child_rinfo->right_relids));
+
+				child_rinfo = commute_restrictinfo(child_rinfo, clause->opno);
+			}
+
+			child_iclause->indexquals = lappend(child_iclause->indexquals, child_rinfo);
+		}
+
+		list_free(rinfo_list);
+		child_iclauses = lappend(child_iclauses, child_iclause);
+	}
+
+	return child_iclauses;
+}
+
 /*
  * reparameterize_path_by_child
  * 		Given a path parameterized by the parent of the given child relation,
@@ -4182,7 +4255,10 @@ do { \
 				IndexPath  *ipath = (IndexPath *) path;
 
 				ADJUST_CHILD_ATTRS(ipath->indexinfo->indrestrictinfo);
-				ADJUST_CHILD_ATTRS(ipath->indexclauses);
+				ipath->indexclauses =
+					build_child_iclauses_multilevel(root,
+													ipath->indexclauses,
+													child_rel, child_rel->top_parent);
 				new_path = (Path *) ipath;
 			}
 			break;
@@ -4261,7 +4337,11 @@ do { \
 
 				REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
-				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
+				jpath->joinrestrictinfo =
+					get_child_restrictinfos_multilevel(root,
+													   jpath->joinrestrictinfo,
+													   child_rel,
+													   child_rel->top_parent);
 				new_path = (Path *) npath;
 			}
 			break;
@@ -4273,8 +4353,16 @@ do { \
 
 				REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
-				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
-				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				jpath->joinrestrictinfo =
+					get_child_restrictinfos_multilevel(root,
+													   jpath->joinrestrictinfo,
+													   child_rel,
+													   child_rel->top_parent);
+				mpath->path_mergeclauses =
+					get_child_restrictinfos_multilevel(root,
+													   mpath->path_mergeclauses,
+													   child_rel,
+													   child_rel->top_parent);
 				new_path = (Path *) mpath;
 			}
 			break;
@@ -4286,8 +4374,16 @@ do { \
 
 				REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
-				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
-				ADJUST_CHILD_ATTRS(hpath->path_hashclauses);
+				jpath->joinrestrictinfo =
+					get_child_restrictinfos_multilevel(root,
+													   jpath->joinrestrictinfo,
+													   child_rel,
+													   child_rel->top_parent);
+				hpath->path_hashclauses =
+					get_child_restrictinfos_multilevel(root,
+													   hpath->path_hashclauses,
+													   child_rel,
+													   child_rel->top_parent);
 				new_path = (Path *) hpath;
 			}
 			break;
@@ -4364,7 +4460,10 @@ do { \
 		new_ppi->ppi_req_outer = bms_copy(required_outer);
 		new_ppi->ppi_rows = old_ppi->ppi_rows;
 		new_ppi->ppi_clauses = old_ppi->ppi_clauses;
-		ADJUST_CHILD_ATTRS(new_ppi->ppi_clauses);
+		new_ppi->ppi_clauses =
+			get_child_restrictinfos_multilevel(root,
+											   new_ppi->ppi_clauses, child_rel,
+											   child_rel->top_parent);
 		new_ppi->ppi_serials = bms_copy(old_ppi->ppi_serials);
 		rel->ppilist = lappend(rel->ppilist, new_ppi);
 
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7266e4cdb..fb86728b71 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -961,10 +961,9 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 							   nappinfos, appinfos);
 
 	/* Construct joininfo list. */
-	joinrel->joininfo = (List *) adjust_appendrel_attrs(root,
-														(Node *) parent_joinrel->joininfo,
-														nappinfos,
-														appinfos);
+	joinrel->joininfo = get_child_restrictinfos(root,
+												parent_joinrel->joininfo,
+												nappinfos, appinfos);
 
 	/*
 	 * Lateral relids referred in child join will be same as that referred in
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index 0b406e9334..854b429ae4 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -16,10 +16,11 @@
 
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/restrictinfo.h"
-
+#include "common/hashfn.h"
 
 static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root,
 												Expr *clause,
@@ -362,6 +363,7 @@ commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op)
 	OpExpr	   *newclause;
 	OpExpr	   *clause = castNode(OpExpr, rinfo->clause);
 
+	clause = castNode(OpExpr, rinfo->clause);
 	Assert(list_length(clause->args) == 2);
 
 	/* flat-copy all the fields of clause ... */
@@ -685,3 +687,255 @@ join_clause_is_movable_into(RestrictInfo *rinfo,
 
 	return true;
 }
+
+/* ----------------------------------------------------------------------------
+ * RestrictInfo hash table interface.
+ *
+ * RestrictInfos applicable to a child relation are obtained by translating the
+ * corresponding RestrictInfos applicable to the parent relation. The same
+ * parent RestictInfo appears in restrictlist or joininfo list of many parent
+ * join relations and thus may get translated multiple times producing multiple
+ * instances of the same child RestrictInfo. In order to avoid that we save the
+ * translated child RestrictInfos in a hash table.  RestrictInfo::rinfo_serial
+ * is unique for every parent RestrictInfo.  RestrictInfo::required_relids
+ * identifies the child relation to which the RestrictInfo is applicable.
+ * Together they uniquely identify a given translated RestictInfo. Hence
+ * together they are used as a key into the hash table.
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * Hash key for RestrictInfo hash table.
+ */
+typedef struct
+{
+	int			rinfo_serial;	/* Same as RestrictInfo::rinfo_serial */
+	Relids		required_relids;	/* Same as RestrictInfo::required_relids */
+} rinfo_tab_key;
+
+/* Hash table entry for RestrictInfo hash table. */
+typedef struct rinfo_tab_entry
+{
+	rinfo_tab_key key;			/* Key must be first. */
+	RestrictInfo *rinfo;
+} rinfo_tab_entry;
+
+/*
+ * rinfo_tab_key_hash
+ *		Computes hash of RestrictInfo hash table key.
+ */
+static uint32
+rinfo_tab_key_hash(const void *key, Size size)
+{
+	rinfo_tab_key *rtabkey = (rinfo_tab_key *) key;
+	uint32		result;
+
+	Assert(sizeof(rinfo_tab_key) == size);
+
+	/* Combine hashes of all components of the key. */
+	result = hash_bytes_uint32(rtabkey->rinfo_serial);
+	result = hash_combine(result, bms_hash_value(rtabkey->required_relids));
+
+	return result;
+}
+
+/*
+ * rinfo_tab_key_match
+ *		Match function for RestrictInfo hash table.
+ */
+static int
+rinfo_tab_key_match(const void *key1, const void *key2, Size size)
+{
+	rinfo_tab_key *rtabkey1 = (rinfo_tab_key *) key1;
+	rinfo_tab_key *rtabkey2 = (rinfo_tab_key *) key2;
+	int			result;
+
+	Assert(sizeof(rinfo_tab_key) == size);
+
+	result = rtabkey1->rinfo_serial - rtabkey2->rinfo_serial;
+	if (result)
+		return result;
+
+	return !bms_equal(rtabkey1->required_relids, rtabkey2->required_relids);
+}
+
+/*
+ * get_restrictinfo_table
+ *		Returns the RestrictInfo hash table from PlannerInfo, creating it if
+ *		necessary.
+ */
+static HTAB *
+get_restrictinfo_table(PlannerInfo *root)
+{
+	if (!root->rinfo_hash)
+	{
+		HASHCTL		hash_ctl = {0};
+
+		hash_ctl.keysize = sizeof(rinfo_tab_key);
+		hash_ctl.entrysize = sizeof(rinfo_tab_entry);
+		hash_ctl.hcxt = root->planner_cxt;
+		hash_ctl.hash = rinfo_tab_key_hash;
+		hash_ctl.match = rinfo_tab_key_match;
+
+		root->rinfo_hash = hash_create("restrictinfo hash table",
+									   1000,
+									   &hash_ctl,
+									   HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
+	}
+
+	return root->rinfo_hash;
+}
+
+/*
+ * add_restrictinfo
+ *		Add the given RestrictInfo to the RestrictInfo hash table.
+ *
+ * The given RestrictInfo should not be present in the hash table. If it does,
+ * multiple instances of the same RestrictInfo may have been created. This
+ * function is a good place to flag that.
+ */
+extern void
+add_restrictinfo(PlannerInfo *root, RestrictInfo *rinfo)
+{
+	HTAB	   *rinfo_hash = get_restrictinfo_table(root);
+	rinfo_tab_key key;
+	rinfo_tab_entry *rinfo_entry;
+	bool		found;
+
+	key.rinfo_serial = rinfo->rinfo_serial;
+	key.required_relids = rinfo->required_relids;
+	rinfo_entry = hash_search(rinfo_hash, &key, HASH_ENTER, &found);
+
+	Assert(!found);
+	rinfo_entry->rinfo = rinfo;
+}
+
+/*
+ * find_restrictinfo
+ *		Return the child RestrictInfo with given rinfo_serial and
+ *		required_relids from RestrictInfo hash table.
+ *
+ * The function returns NULL if it does not find the required RestrictInfo in
+ * the hash table.
+ */
+RestrictInfo *
+find_restrictinfo(PlannerInfo *root, int rinfo_serial, Relids required_relids)
+{
+	HTAB	   *rinfo_hash = get_restrictinfo_table(root);
+	rinfo_tab_entry *rinfo_entry;
+	rinfo_tab_key key;
+
+	key.rinfo_serial = rinfo_serial;
+	key.required_relids = required_relids;
+	rinfo_entry = hash_search(rinfo_hash, &key,
+							  HASH_FIND,
+							  NULL);
+	return (rinfo_entry ? rinfo_entry->rinfo : NULL);
+}
+
+/*
+ * get_child_restrictinfos
+ * 		Returns list of child RestrictInfos obtained by
+ *		translating the given parent RestrictInfos according to the given
+ *		AppendRelInfos.
+ *
+ * If a required translated RestrictInfo is available in the RestrictInfo hash
+ * table, the function includes the available child RestrictInfo to the list.
+ * Otherwise, it translates the corresponding parent RestrictInfo and adds it to
+ * the RestrictInfo hash table.
+ */
+List *
+get_child_restrictinfos(PlannerInfo *root, List *parent_restrictinfos,
+						int nappinfos, AppendRelInfo **appinfos)
+{
+	List	   *child_clauselist = NIL;
+
+	foreach_node(RestrictInfo, parent_rinfo, parent_restrictinfos)
+	{
+		Relids		child_req_relids;
+		RestrictInfo *child_rinfo = NULL;
+
+		child_req_relids = adjust_child_relids(parent_rinfo->required_relids,
+											   nappinfos, appinfos);
+
+		if (bms_equal(child_req_relids, parent_rinfo->required_relids))
+		{
+			/*
+			 * If no relid was translated, child's RestrictInfo is same as
+			 * that of parent.
+			 */
+			child_rinfo = parent_rinfo;
+		}
+		else
+		{
+			child_rinfo = find_restrictinfo(root, parent_rinfo->rinfo_serial, child_req_relids);
+
+			/*
+			 * This function may be called thousands of times when there are
+			 * thousands of partitions involved. We won't require the
+			 * translated child relids further. Hence free those to avoid
+			 * accumulating huge amounts of memory.
+			 */
+			bms_free(child_req_relids);
+		}
+
+		if (!child_rinfo)
+		{
+			child_rinfo = castNode(RestrictInfo,
+								   adjust_appendrel_attrs(root, (Node *) parent_rinfo,
+														  nappinfos, appinfos));
+			add_restrictinfo(root, child_rinfo);
+		}
+
+		child_clauselist = lappend(child_clauselist, child_rinfo);
+	}
+
+	return child_clauselist;
+}
+
+/*
+ * get_child_restrictinfos_multilevel
+ *		Similar to get_child_restrictinfos() but for translations through multiple
+ *		levels of partitioning hierarchy.
+ *
+ * This function is similar to adjust_appendrel_attrs_multilevel() except that
+ * this function makes use of RestrictInfo hash table.
+ */
+List *
+get_child_restrictinfos_multilevel(PlannerInfo *root, List *parent_clauselist,
+								   RelOptInfo *child_rel, RelOptInfo *top_parent)
+{
+	AppendRelInfo **appinfos;
+	int			nappinfos;
+	List	   *tmp_clauselist = parent_clauselist;
+	List	   *child_clauselist;
+
+	/* Recursively traverse up the partition hierarchy. */
+	if (child_rel->parent != top_parent)
+	{
+		if (child_rel->parent)
+		{
+			tmp_clauselist = get_child_restrictinfos_multilevel(root,
+																parent_clauselist,
+																child_rel->parent,
+																top_parent);
+		}
+		else
+			elog(ERROR, "child_rel is not a child of top_parent");
+	}
+
+	appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos);
+	child_clauselist = get_child_restrictinfos(root, tmp_clauselist,
+											   nappinfos, appinfos);
+
+	/*
+	 * This function will be called thousands of times, if there are thousands
+	 * of partitions involved. Free temporary objects created in this function
+	 * to avoid accumulating huge memory.
+	 */
+	pfree(appinfos);
+	if (tmp_clauselist != parent_clauselist)
+		list_free(tmp_clauselist);
+
+	return child_clauselist;
+}
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 14ccfc1ac1..d11de3775f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -553,6 +553,12 @@ struct PlannerInfo
 
 	/* Does this query modify any partition key columns? */
 	bool		partColsUpdated;
+
+	/*
+	 * Hash table to save translated RestrictInfos while planning
+	 * partitionwise join.
+	 */
+	struct HTAB *rinfo_hash pg_node_attr(read_write_ignore);
 };
 
 
diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
index 1b42c832c5..c0624ad8c8 100644
--- a/src/include/optimizer/restrictinfo.h
+++ b/src/include/optimizer/restrictinfo.h
@@ -47,5 +47,15 @@ extern bool join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel);
 extern bool join_clause_is_movable_into(RestrictInfo *rinfo,
 										Relids currentrelids,
 										Relids current_and_outer);
+extern RestrictInfo *find_restrictinfo(PlannerInfo *root, int rinfo_serial,
+									   Relids child_required_relids);
+extern void add_restrictinfo(PlannerInfo *root, RestrictInfo *child_rinfo);
+extern List *get_child_restrictinfos(PlannerInfo *root,
+									 List *parent_restrictinfos,
+									 int nappinfos, AppendRelInfo **appinfos);
+extern List *get_child_restrictinfos_multilevel(PlannerInfo *root,
+												List *parent_clauselist,
+												RelOptInfo *child_rel,
+												RelOptInfo *top_parent);
 
 #endif							/* RESTRICTINFO_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 547d14b3e7..bac0792834 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -395,6 +395,7 @@ CheckPointStmt
 CheckpointStatsData
 CheckpointerRequest
 CheckpointerShmemStruct
+ChildRinfoTabEntry
 Chromosome
 CkptSortItem
 CkptTsStatus
@@ -3864,6 +3865,8 @@ rewind_source
 rewrite_event
 rf_context
 rfile
+rinfo_tab_entry
+rinfo_tab_key
 rm_detail_t
 role_auth_extra
 rolename_hash
-- 
2.34.1

Reply via email to