diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index b1bbd2fd6b7..a4a2809ac70 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -118,6 +118,7 @@ remove_useless_outer_joins(PlannerInfo *root)
 		SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
 		int			innerrelid;
 		int			nremoved;
+		RangeTblEntry *rte;
 
 		/* Skip if not removable */
 		if (!join_is_removable(root, sjinfo))
@@ -131,13 +132,11 @@ remove_useless_outer_joins(PlannerInfo *root)
 		innerrelid = bms_singleton_member(sjinfo->syn_righthand);
 
 		/*
-		 * The removed rel had better not be one that the rest of the planner
-		 * expects to find.  It can't be, really: a result relation or a
-		 * row-marked relation has junk columns in the query targetlist, and
+		 * The removed rel had better not be row-marked.  It can't be, really:
+		 * a row-marked relation has junk columns in the query targetlist, and
 		 * join_is_removable would have seen those as uses of the rel from
 		 * "relation 0".  But let's check.
 		 */
-		Assert(innerrelid != root->parse->resultRelation);
 #ifdef USE_ASSERT_CHECKING
 		foreach_node(PlanRowMark, rc, root->rowMarks)
 			Assert(rc->rti != innerrelid && rc->prti != innerrelid);
@@ -159,12 +158,15 @@ remove_useless_outer_joins(PlannerInfo *root)
 		 * As in pull_up_simple_subquery, discard no-longer-needed subqueries.
 		 * This is not just an optimization, but is necessary to prevent
 		 * subsequent processing from descending into stale subtrees and
-		 * seeing inconsistent data.  (Although simple_rte_array[] will be
-		 * rebuilt shortly, we can still use it to access the correct RTE in
-		 * the parse tree.)
+		 * seeing inconsistent data.  Likewise discard any securityQuals,
+		 * which contain Vars of the removed rel.  (Although
+		 * simple_rte_array[] will be rebuilt shortly, we can still use it to
+		 * access the correct RTE in the parse tree.)
 		 */
-		if (root->simple_rte_array[innerrelid]->rtekind == RTE_SUBQUERY)
-			root->simple_rte_array[innerrelid]->subquery = NULL;
+		rte = root->simple_rte_array[innerrelid];
+		if (rte->rtekind == RTE_SUBQUERY)
+			rte->subquery = NULL;
+		rte->securityQuals = NIL;
 
 		/*
 		 * It's okay to keep scanning join_info_list for more removable joins,
@@ -488,8 +490,13 @@ reduce_unique_semijoins(PlannerInfo *root)
 		if (sjinfo->jointype != JOIN_SEMI)
 			continue;
 
-		if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid))
+		/*
+		 * We test the syntactic righthand side, since that's what identifies
+		 * the JoinExpr we'll modify.
+		 */
+		if (!bms_get_singleton_member(sjinfo->syn_righthand, &innerrelid))
 			continue;
+		Assert(bms_equal(sjinfo->min_righthand, sjinfo->syn_righthand));
 
 		innerrel = find_base_rel(root, innerrelid);
 
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 9792a6c752f..a15d1cbede4 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -28,7 +28,6 @@
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/planmain.h"
-#include "parser/parsetree.h"
 
 
 /*
@@ -324,22 +323,6 @@ restart:
 	 */
 	setup_eager_aggregation(root);
 
-	/*
-	 * If there's a result relation, initialize all_result_relids to include
-	 * it; and if we've verified that it is non-inheriting, mark it as a leaf
-	 * target.  add_other_rels_to_query() will expand these sets if the result
-	 * relation has children.
-	 */
-	if (parse->resultRelation)
-	{
-		RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable);
-
-		root->all_result_relids = bms_make_singleton(parse->resultRelation);
-		if (!rte->inh)
-			root->leaf_result_relids =
-				bms_make_singleton(parse->resultRelation);
-	}
-
 	/*
 	 * Now expand appendrels by adding "otherrels" for their children.  We
 	 * delay this to the end so that we have as much information as possible
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c3c158a253d..a605387dcaa 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -803,8 +803,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
 	root->eq_classes = NIL;
 	root->ec_merging_done = false;
 	root->last_rinfo_serial = 0;
-	root->all_result_relids = NULL;
-	root->leaf_result_relids = NULL;
+	root->all_result_relids =
+		parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
+	root->leaf_result_relids = NULL;	/* we'll find out leaf-ness later */
 	root->append_rel_list = NIL;
 	root->row_identity_vars = NIL;
 	root->rowMarks = NIL;
@@ -977,6 +978,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
 											list_length(rte->securityQuals));
 	}
 
+	/*
+	 * If we have now verified that the query target relation is
+	 * non-inheriting, mark it as a leaf target.
+	 */
+	if (parse->resultRelation)
+	{
+		RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+		if (!rte->inh)
+			root->leaf_result_relids =
+				bms_make_singleton(parse->resultRelation);
+	}
+
 	/*
 	 * This would be a convenient time to check access permissions for all
 	 * relations mentioned in the query, since it would be better to fail now,
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 52157d980b5..3653f00d383 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -534,8 +534,9 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
  * RangeTblRef and JoinExpr.
  *
  * Also, new_index can be INVALID_VAR to indicate that we are deleting the
- * given relid from the tree.  In this case we should only find rt_index
- * in nullingrels sets, never in any varno field.
+ * given relid from the tree.  In this case we expect to find rt_index only
+ * in Relids fields (varnullingrels, phnullingrels, phrels), never in any
+ * field that identifies a single relation.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.  The given expression tree should have been copied
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index f42085e53a0..356f118a366 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -536,6 +536,17 @@ NOTICE:  f_leak => awesome science fiction
    9 |  22 |      1 | regress_rls_dave  | awesome science fiction
 (4 rows)
 
+-- a rel with RLS quals can still be removed by outer-join removal
+EXPLAIN (COSTS OFF)
+SELECT c.cid FROM category c LEFT JOIN document d ON c.cid = d.did;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Seq Scan on category c
+   InitPlan expr_1
+     ->  Index Scan using uaccount_pkey on uaccount
+           Index Cond: (pguser = CURRENT_USER)
+(4 rows)
+
 -- viewpoint from regress_rls_carol
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index ed70b1b229e..99eaba28f5a 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -307,6 +307,10 @@ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
 SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
   WHERE f_leak(dtitle) ORDER BY did;
 
+-- a rel with RLS quals can still be removed by outer-join removal
+EXPLAIN (COSTS OFF)
+SELECT c.cid FROM category c LEFT JOIN document d ON c.cid = d.did;
+
 -- viewpoint from regress_rls_carol
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
