From ac6b01c65cf9d824edcf194c971d477637155095 Mon Sep 17 00:00:00 2001
From: Richard Guo <guofenglinux@gmail.com>
Date: Thu, 4 May 2023 11:37:52 +0800
Subject: [PATCH v3 1/4] Allow direct lookups of SpecialJoinInfo by ojrelid

There are several places where we need to scan all the SpecialJoinInfos
in join_info_list looking for SpecialJoinInfos that belong to a given
outer join relid set.  This is usually expensive since it is an O(n)
operation.  So introduce join_info_array to allow direct lookups of
SpecialJoinInfo by ojrelid.  This can benefit clause_is_computable_at()
and have_unsafe_outer_join_ref(), and maybe more future usages.
---
 src/backend/optimizer/path/joinpath.c     | 27 +++++++++++------------
 src/backend/optimizer/plan/analyzejoins.c |  6 +++++
 src/backend/optimizer/plan/initsplan.c    |  5 +++++
 src/backend/optimizer/util/relnode.c      |  7 ++++++
 src/backend/optimizer/util/restrictinfo.c | 25 +++++++++++----------
 src/include/nodes/pathnodes.h             |  9 ++++++++
 6 files changed, 53 insertions(+), 26 deletions(-)

diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index cd80e61fd7..40283483a1 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -390,30 +390,29 @@ have_unsafe_outer_join_ref(PlannerInfo *root,
 	bool		result = false;
 	Relids		unsatisfied = bms_difference(inner_paramrels, outerrelids);
 	Relids		satisfied = bms_intersect(inner_paramrels, outerrelids);
+	Relids		unsatisfied_ojrelids;
+	int			i;
 
-	if (bms_overlap(unsatisfied, root->outer_join_rels))
+	unsatisfied_ojrelids = bms_intersect(unsatisfied, root->outer_join_rels);
+
+	i = -1;
+	while ((i = bms_next_member(unsatisfied_ojrelids, i)) >= 0)
 	{
-		ListCell   *lc;
+		SpecialJoinInfo *sjinfo = root->join_info_array[i];
 
-		foreach(lc, root->join_info_list)
+		if (bms_overlap(satisfied, sjinfo->min_righthand) ||
+			(sjinfo->jointype == JOIN_FULL &&
+			 bms_overlap(satisfied, sjinfo->min_lefthand)))
 		{
-			SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
-
-			if (!bms_is_member(sjinfo->ojrelid, unsatisfied))
-				continue;		/* not relevant */
-			if (bms_overlap(satisfied, sjinfo->min_righthand) ||
-				(sjinfo->jointype == JOIN_FULL &&
-				 bms_overlap(satisfied, sjinfo->min_lefthand)))
-			{
-				result = true;	/* doesn't work */
-				break;
-			}
+			result = true;	/* doesn't work */
+			break;
 		}
 	}
 
 	/* Waste no memory when we reject a path here */
 	bms_free(unsatisfied);
 	bms_free(satisfied);
+	bms_free(unsatisfied_ojrelids);
 
 	return result;
 }
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 98cf3494e6..b569239c99 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -107,6 +107,8 @@ restart:
 		 * immediately, we don't bother with foreach_delete_current.)
 		 */
 		root->join_info_list = list_delete_cell(root->join_info_list, lc);
+		if (sjinfo->ojrelid != 0)
+			root->join_info_array[sjinfo->ojrelid] = NULL;
 
 		/*
 		 * Restart the scan.  This is necessary to ensure we find all
@@ -681,6 +683,10 @@ reduce_unique_semijoins(PlannerInfo *root)
 
 		/* OK, remove the SpecialJoinInfo from the list. */
 		root->join_info_list = foreach_delete_current(root->join_info_list, lc);
+		/*
+		 * There is no need to change join_info_array since we do not store
+		 * SpecialJoinInfos for semijoins there.
+		 */
 	}
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 06f90882c4..857f7181d2 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -1232,7 +1232,12 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem)
 
 		/* And add the SpecialJoinInfo to join_info_list */
 		if (sjinfo)
+		{
 			root->join_info_list = lappend(root->join_info_list, sjinfo);
+
+			if (sjinfo->ojrelid != 0)
+				root->join_info_array[sjinfo->ojrelid] = sjinfo;
+		}
 	}
 	else
 	{
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 68fd033595..97ea14667b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -117,6 +117,13 @@ setup_simple_rel_arrays(PlannerInfo *root)
 		root->simple_rte_array[rti++] = rte;
 	}
 
+	/*
+	 * join_info_array is initialized to all NULLs and will be filled by
+	 * later calls to deconstruct_distribute().
+	 */
+	root->join_info_array = (SpecialJoinInfo **)
+		palloc0(size * sizeof(SpecialJoinInfo *));
+
 	/* append_rel_array is not needed if there are no AppendRelInfos */
 	if (root->append_rel_list == NIL)
 	{
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index c44bd2f815..4961cf3cbf 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -544,25 +544,26 @@ clause_is_computable_at(PlannerInfo *root,
 						Relids clause_relids,
 						Relids eval_relids)
 {
-	ListCell   *lc;
+	Relids		relevant_relids;
+	int			i;
 
 	/* Nothing to do if no outer joins have been performed yet. */
 	if (!bms_overlap(eval_relids, root->outer_join_rels))
 		return true;
 
-	foreach(lc, root->join_info_list)
-	{
-		SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
-
-		/* Ignore outer joins that are not yet performed. */
-		if (!bms_is_member(sjinfo->ojrelid, eval_relids))
-			continue;
+	/*
+	 * Calculate relevant_relids as being the outer joins that have been
+	 * evaluated and not listed in the clause's nullingrels.
+	 */
+	relevant_relids = bms_intersect(eval_relids, root->outer_join_rels);
+	relevant_relids = bms_difference(relevant_relids, clause_relids);
 
-		/* OK if clause lists it (we assume all Vars in it agree). */
-		if (bms_is_member(sjinfo->ojrelid, clause_relids))
-			continue;
+	i = -1;
+	while ((i = bms_next_member(relevant_relids, i)) >= 0)
+	{
+		SpecialJoinInfo *sjinfo = root->join_info_array[i];
 
-		/* Else, trouble if clause mentions any nullable Vars. */
+		/* Trouble if clause mentions any nullable Vars. */
 		if (bms_overlap(clause_relids, sjinfo->min_righthand) ||
 			(sjinfo->jointype == JOIN_FULL &&
 			 bms_overlap(clause_relids, sjinfo->min_lefthand)))
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index cf28416da8..ab921b3bb3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -248,6 +248,15 @@ struct PlannerInfo
 	 */
 	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
+	/*
+	 * join_info_array is the same length as the above arrays, and holds
+	 * pointers to the corresponding SpecialJoinInfo entry indexed by ojrelid,
+	 * or NULL if the RTE is not an outer join.  Note that we do not store
+	 * SpecialJoinInfos with 0 ojrelid in this array.  (Not printed because
+	 * it'd be redundant with join_info_list.)
+	 */
+	struct SpecialJoinInfo **join_info_array pg_node_attr(read_write_ignore);
+
 	/*
 	 * all_baserels is a Relids set of all base relids (but not joins or
 	 * "other" rels) in the query.  This is computed in deconstruct_jointree.
-- 
2.31.0

